Service Client

The Service Client interacts with Services for interacting with authorizations and sessions. The Service Client can be obtained from the appropriate factories.

Authorize a Transaction

The authorization process begins with a Create Authorization Request call. In its most basic implementation, an End User Username is all that is provided. The call returns an AuthorizationRequest object that contains the auth request ID and push package. The auth request ID is used to determine the End User’s response through either: 1) polling with Get Authorization Response or 2) asynchronously via a webhook with Handle Webhook. Currently, only one pending Authorization request can exist at one time. In the case that another request is made before either the end user responds or the Authorization request expires, an AuthorizationInProgress exception is raised.

var username = "myuser";
var authorizationRequest = serviceClient.CreateAuthorizationRequest(user: username, context: null, policy: null
title: null, ttl: null, pushTitle: null, pushBody: null, denialReasons: null);
var authorizationRequestId = authorizationRequest.Id;
String username = "myuser";
AuthorizationRequest authorizationRequest = serviceClient.createAuthorizationRequest(username, null, null,
null, null, null, null, null);
String authorizationRequestId = authorization.getId();
username = "myuser"
auth_request_id = authorization_request.auth_request
authorization_request = service_client.authorization_request(username)

Dealing with Pre-Existing Authorizations

Only one Authorization can exist at one time for a user. In the case that an authorization attempt is made when one already exists, an AuthorizationInProgress exception will be raised. This exception will have additional contextual attributes to aid in this process.

from same service:Boolean stating whether the Authorization in progress is from the same Service requesting the new Authorization Request.
authorization request id:Identifier of the existing Authorization Request that caused this exception.
expires:When the Authorization Request identified by authorization request id will expire.
var username = "myuser";
try
{
    var authorizationRequest = serviceClient.CreateAuthorizationRequest(user: username, context: null, policy: null
    title: null, ttl: null, pushTitle: null, pushBody: null, denialReasons: null);
}
catch(AuthorizationInProgress e)
{
    Console.WriteLine("Authorization Request already in progress");
    Console.WriteLine("From Same Service    :" + e.FromSameService);
    Console.WriteLine("Existing Auth ID     :" + e.AuthorizationRequestId);
    Console.WriteLine("Auth Expiration Time :" + e.Expires);
    return 1;
}
String username = "myuser";
try
{
    AuthorizationRequest authorizationRequest = serviceClient.createAuthorizationRequest(
            username, null, null, null, null, null, null, null);
} catch (AuthorizationInProgress e) {
    System.out.println("Authorization Request already in progress");
    System.out.println("From Same Service    :" + e.isFromSameService());
    System.out.println("Existing Auth ID     :" + e.getAuthorizationRequestId());
    System.out.println("Auth Expiration Time :" + e.getExpires());
}
from launchkey.exceptions import AuthorizationInProgress
username = "myuser"
try:
    auth = service_client.authorization_request(username)
except AuthorizationInProgress as in_progress:
    print("Authorization Request already in progress")
    print("From Same Service   : %s" % in_progress.from_same_service)
    print("Existing Auth ID    : %s" % in_progress.authorization_request_id)
    print("Auth Expiration Time: %s" % in_progress.expires.strftime("%m/%d/%Y %H:%M:%S UTC"))

Adding Context

Context can be added to the Create Authorization Request call by passing a string value as the second parameter of the method. The context allows the user to have confidence that they are approving the correct request.

var username = "myuser";
var context = "Access to rear door requested";
var authorizationRequest = serviceClient.CreateAuthorizationRequest(username: username, context: context,
policy: null, title: null, ttl: null, pushTitle: null, pushBody: null, denialReasons: null);
String username = "myuser";
String context = "Access to rear door requested";
AuthorizationRequest authorizationRequest = serviceClient.createAuthorizationRequest(username, context, null,
null, null, null, null, null);
username = "myuser"
context = "Access to rear door requested"
authorization_request = service_client.authorization_request(username, context=context)

Adding Custom Titles and Push Messaging

Authorization requests can be customized with titles and push messaging. Three parameters (title, push title, and push body) can be used to modify the authorization request as described below. NOTE: All of these parameters are only valid for a Directory Service, and will return an error if used with an Organization Service.

title:The title for an individual Authorization Request from a Directory Service.
push title:The push title for a push notification originating from a Directory Service. By default no push title will be included.
push body:The push message body for a push notification originating from a Directory Service. The default message is used unless a new body is included.
var username = "myuser";
var title = "Custom Request";
var pushTitle = "You have a pending request";
var pushBody = " Do you wish to authorize this request?";
var authorizationRequest = serviceClient.CreateAuthorizationRequest(username, context: null, policy: null,
                            title: title, ttl: null, pushTitle: pushTitle, pushBody: pushBody,
                            denialReasons: null);
String username = "myuser";
String title = "Custom Request";
String pushTitle = "You have a pending request";
String pushBody = " Do you wish to authorize this request?";
AuthorizationRequest authorizationRequest = ServiceClient.createAuthorizationRequest(username,
                                            null, null, title, null, pushTitle, pushBody, null);
username = "myuser"
title = "Custom Request"
push_title = "You have a pending request"
push_body = " Do you wish to authorize this request?"
authorization_request = service_client.authorization_request(username, title=title, push_title=push_title,
                                                             push_body=push_body)

Adding an Expiration Time

Expiration Times can be set for an individual authorization request through a Time To Live (ttl) parameter. The ttl can be an integer value between 30 and 600 seconds. The default value is 300 if an expiration time is not provided.

var username = "myuser";
var ttl = 60;
var authorizationRequest = serviceClient.CreateAuthorizationRequest(username, context: null, policy: null,
                            title: null, ttl: ttl, pushTitle: null, pushBody: null,
                            denialReasons: null);
String username = "myuser";
Integer ttl = 60;
AuthorizationRequest authorizationRequest = ServiceClient.createAuthorizationRequest(username,
                                            null , null, null, ttl, null, null, null);
username = "myuser"
ttl = 60
authorization_request = service_client.authorization_request(username, ttl=ttl)

Adding Custom Denial Reasons to Authorization Requests

The Authorization Request can also include a list array of two or more “denial reason” entries shown to an end user when the user denies the Authorization Request. This parameter returns an error if used with an Organization Service. This parameter will also return an error if denial_context_inquiry_enabled is not set to true (see Organization Client). Both the id and reason attributes for a denial reason must be unique amongst the entire set of denial reasons.

id:A string mapping of 1 to 5 characters representing the reason for the denial. This value exists in an authorization response package.
reason:The text of the denial reason keyed to the id value and displayed to the user. Only this parameter is shown to the user.
fraud:A flag to indicate fraud as the denial reason. At least one of the items must have a fraud flag of true.
var username = "myuser";
var denialReasons = new List<DenialReason>(){
    new DenialReason(){id="a", reason="Absolute fraud", fraud=True},
    new DenialReason(){id="b", reason="Bad Timing", fraud=False},
    new DenialReason(){id="c", reason="I changed my mind", fraud=False},
};
var authorizationRequest = serviceClient.CreateAuthorizationRequest(username, context: null, policy: null,
                            title: null, ttl: ttl, pushTitle: null, pushBody: null,
                            denialReasons: denialReasons);
String username = "myuser";
List<DenialReason> denialReasons = new List<DenialReason>(){{
    add("a", "Absolute fraud", True);
    add("b", "Bad timing", False);
    add("c", "I changed my mind", False);
}};
AuthorizationRequest authorizationRequest = serviceClient.createAuthorizationRequest(userIdentifier,
                                            null , null, null, ttl, null, null, denialReasons);
from launchkey.entities.service import DenialReason

username = "myuser"
denial_reasons = [
    DenialReason("a", "Absolute fraud", True),
    DenialReason("b", "Bad timing", False),
    DenialReason("c", "I changed my mind", False)
]
authorization_request = service_client.authorization_request(username, denial_reasons=denial_reasons)

Add Dynamic Policies

Authorization policies can be set statically on the Admin Center or through updating the Service via API calls (see Service Management). They can also be passed dynamically with the Create Authorization Request call. The policy can be submitted as the third argument of the method call.

Factor Quantity Example:

var username = "myuser";
var context = "Two factor policy request";
var authPolicy = new AuthPolicy(2);
var authorizationRequest = serviceClient.CreateAuthorizationRequest(username: username, context: context,
policy: authPolicy, title: null, ttl: null, pushTitle: null, pushBody: null, denialReasons: null);
String username = "myuser";
String context = "Two factor policy request";
AuthPolicy authPolicy = new AuthPolicy(2);
String authorizationRequest = serviceClient.createAuthorizationRequest(username, context, authPolicy, null,
null, null, null, null);
from launchkey.clients.service import AuthPolicy

username = "myuser"
context = "Two factor policy request"
auth_policy = AuthPolicy(any=2)
authorization_request = service_client.authorization_request(username, context=context, policy=auth_policy)

Factor Type Example:

var username = "myuser";
var context = "Knowledge and Inherence factors required policy request";
var authPolicy = new AuthPolicy(requireKnowledgeFactor: true, requireInherenceFactor: true,
                                requirePossessionFactor: false);
var authorizationRequest = serviceClient.CreateAuthorizationRequest(username: username, context: context,
policy: authPolicy, title: null, ttl: null, pushTitle: null, pushBody: null, denialReasons: null);
String username = "myuser";
String context = "Knowledge and Inherence factors required policy request";
boolean requireKnowledgeFactor = true;
boolean requireInherenceFactor = true;
boolean requirePossessionFactor = false;
AuthPolicy authPolicy = new AuthPolicy(requireKnowledgeFactor, requireInherenceFactor, requirePossessionFactor);
String authorizationRequest = serviceClient.createAuthorizationRequest(username, context, authPolicy, null,
                                                                        null, null, null, null);
from launchkey.clients.service import AuthPolicy

username = "myuser"
context = "Knowledge and Inherence factors required policy request"
require_knowledge_factor = True
require_inherence_factor = True
require_possession_factor = False
auth_policy = AuthPolicy(knowledge=require_knowledge_factor, inherence=require_inherence_factor,
                         possession=require_possession_factor)
authorization_request = service_client.authorization_request(username, context=context,
                                                             policy=auth_policy)

Geofence Type Example:

var username = "myuser";
var context = "Taj Mahal Geofence policy request";
var locations = new List<Location>;
var radiusMeters = 175 + 20; // 1/3 the longest side of the building + 20m for GPS error.
var latitudeDegrees = 27.1750;
var longitudeDegrees = 78.0422;
locations.add(new Location(radiusMeters, latitudeDegrees, longitudeDegrees));
AuthPolicy authPolicy = new AuthPolicy(locations: locations);
var authorizationRequest = serviceClient.CreateAuthorizationRequest(username: username, context: context,
policy: authPolicy, title: null, ttl: null, pushTitle: null, pushBody: null, denialReasons: null);
String username = "myuser";
String context = "Taj Mahal Geofence policy request";
List<AuthPolicy.Location> locations = new ArrayList<AuthPolicy.Location>;
double radiusMeters = 175 + 20; // 1/3 the longest side of the building + 20m for GPS error.
double latitudeDegrees = 27.1750;
double longitudeDegrees = 78.0422;
locations.add(new AuthPolicy.Location(radiusMeters, latitudeDegrees, longitudeDegrees));
AuthPolicy authPolicy = new AuthPolicy(locations);
String authorizationRequest = serviceClient.createAuthorizationRequest(username, context, authPolicy, null,
null, null, null, null);
from launchkey.clients.service import AuthPolicy

username = "myuser"
context = "Taj Mahal Geofence policy request"
radius_meters = 175 + 20 # 1/3 the longest side of the building + 20m for GPS error.
latitude_degrees = 27.1750
longitude_degrees = 78.0422
auth_policy = AuthPolicy()
auth_policy.add_geofence(latitude=latitude_degrees, longitude=longitude_degrees, radius=radius_meters)
authorization_request = service_client.authorization_request(username, context=context, policy=auth_policy)

Fetching the End User’s Response

An Authorization Response will contain various attributes, which can be retrieved when using either polling or webhooks.

Authorization Request ID:

The unique identifier for the authorization request. When using webhooks, it is required that you have a way for the callback handler to alert your internal authentication mechanism of the response from the user based on this value. This value can also be passed into the Session Start method to keep track of the auth linked to a session.

Authorized:

Contains a boolean value representing whether the User approved the authorization request.

Fraud:

A flag to indicate whether a user denial was marked as fraud based on their response.

Service User Hash:

This is a value unique to the Service and User combination. This value is the identifying id sent in the service user session end webhook if a user session is terminated remotely. If the Authorized value is true and you wish to support remote logout, you will need to associate this value with the session created by the response. The Service User Hash may also be used to ensure that you have not been sent a response for and authorization request from another Service for the same user.

Organization User Hash:

This value is unique to the Organization and User combination. This value may be used to ensure that a response is not sent for an authorization request from another Organization for the same user.

User Push ID:

This value is unique to the Service and User combination. This value can be used in place of a username in all communication where a username is required. Using this value rather than the user name can improve security by not storing a value that is valid across Services, Directories, and Organizations. If the user push ID is obtained by an attacker, the only Service the attacker could use this against would be the service for which it was generated.

Device ID:

This is a value unique to the term:User that identifies the device used to respond to the authorization request.

Service PINs:

This value is a FIFO buffer as a list of strings which are unique to the Service and Device. This list can aid in detecting device cloning. Over time, the original device and the cloned device will no longer contain any of the same values in the list they return in the authorization response.

Warning

Service PINs are an advanced feature that have the possibility of generating false negatives when a valid device loses synchronization with your application. If you use Service PINs to detect device cloning, you will need to provide a way to re-synchronize the user’s device.

Reason:

The reason for the response returned by the user. See Auths for a list of response reasons.

Type:

The response type returned by the user. See Auths for a list of response types.

Denial Reason:

A unique key identifying the reason the user denied a request. This value will match the id that was submitted in the Denial Reasons if any were given in the Create Authorization Request call.

Warning

Although the functionality exists to fetch for a response, Webhooks are the preferred method for completing a Login or Authorization request. If your implementation is not externally available or cannot receive HTTP requests, you may need to resort to polling.

The Get Authorization Response method can poll for the status of an existing authorization request and pass the authorization request identifier returned by the Create Authorization Request call.

Polling Example:

var username = "myuser";
var authorizationRequest = serviceClient.CreateAuthorizationRequest(user: username, context: null, policy: null
title: null, ttl: null, pushTitle: null, pushBody: null, denialReasons: null);
AuthResponse authResponse = null;

while (authResponse == null)
{
    Thread.sleep(1000);
    authResponse = serviceClient.GetAuthResponse(authorizationRequest);
    if (authResponse != null)
    {
        var authorized = authResponse.Authorized;
        // handle authorization result
    }
}
String username = "myuser";
String authorizationRequest = serviceClient.createAuthorizationRequest(username, null, null
null, null, null, null, null);
AuthResponse authResponse = null;

while (authResponse == null) {
    Thread.sleep(1000L);
    authResponse = serviceClient.getAuthResponse(authorizationRequest);
    if (authResponse != null) {
        boolean authorized = authResponse.isAuthorized();
        // handle authorization result
    }
}
from launchkey.exceptions import RequestTimedOut
from time import sleep

user = "my_unique_internal_identifier"
authorization_request = service_client.authorization_request(user)
authorization_request_id = authorization_request.auth_request
response = None
try:
    while response is None:
        sleep(1)
        response = service_client.get_authorization_response(authorization_request_id)
        if response is not None:
            if response.authorized is True:
                # User logged in
            else:
                # User denied the auth request
except RequestTimedOut:
    # The user did not respond to the request in the timeout period (auth ttl)

Helper attributes can quickly determine whether an authorization request is approved or denied (see previous code examples). Additional context can also be derived using type and reason attributes in the Authorization Response object.

Response Type and Reason Example:

var username = "myuser";
var authorizationRequest = serviceClient.CreateAuthorizationRequest(user: username, context: null, policy: null
title: null, ttl: null, pushTitle: null, pushBody: null, denialReasons: null);
var authorizationRequestId = authorizationRequest.Id;

AuthResponse authResponse = null;

try
{
while (authResponse == null)
{
    Thread.sleep(1000);
    authResponse = serviceClient.GetAuthorizationResponse(authorizationRequestId);
    if (authResponse != null)
    {
        if (AuthorizationResponseType.Type == AuthorizationResponseType.AUTHORIZED)
        {
            //User logged in
            Console.Writeline("Logged in");
        }
        else if (AuthorizationResponseType.Type == AuthorizationResponseType.DENIED)
        {
            if (authResponse.Reason == AuthorizationResponseReason.FRAUDULENT)
            {
                // User flagged request as fraudulent
                Console.Writeline("Fraud");
            }
            // Denial is handled along with Denial Reason
            Console.Writeline("Denied due to denial id: " + authResponse.denialReason);

        }
        else if (AuthorizationResponseType.Type == AuthorizationResponseType.FAILED)
        {
            // The auth request failed due to one of the documented failure types. They can be accessed
            // and handled individually, but let's just log the reason.
            Console.WriteLine("User auth failed due to: " + authResponse.Reason);
            // Handle failure
        }
        else
        {
            // Unknown type
            Console.WriteLine("Unknown response type");
        }
    }

}
}
catch(AuthorizationRequestTimedOutError)
{
    // The user did not respond to the request in the timeout period (5 minutes)
    Console.WriteLine("user never replied.");
    return 1;
}
String username = "myuser";
AuthorizationRequest authorizationRequest = serviceClient.createAuthorizationRequest(username, null, null,
null, null, null, null, null);
String authorizationRequestId = authorizationRequest.getId();

AuthResponse authResponse = null;

try
{
while (authResponse == null)
{
    Thread.sleep(1000L);
    authResponse = serviceClient.GetAuthorizationResponse(authorizationRequestId);
    if (authResponse != null)
    {
        if (authResponse.getType() == AuthorizationResponse.Type.AUTHORIZED)
        {
        //User logged in
        System.out.println("Logged in");
        }
        else if (authResponse.getType() == AuthorizationResponse.Type.DENIED)
        {
            if (authResponse.getReason() == AuthorizationResponse.Reason.FRAUDULENT)
            {
                // User flagged request as fraudulent
                System.out.println("Fraud");
            }
            // Denial is handled along with Denial Reason
            System.out.println("Denied due to denial id: " + authResponse.getDenialReason());
        }
        else if (authResponse.getType() == AuthorizationResponse.Type.FAILED)
        {
            // The auth request failed due to one of the documented failure types. They can be accessed
            // and handled individually, but let's just log the reason.
            System.out.println("User auth failed due to: " + authResponse.getReason();
            // Handle failure
        }
        else
        {
            // Unknown type
            System.out.println("Unknown response type");
        }
    }

}
}
catch (AuthorizationRequestTimedOutError e)
{
    // The user did not respond to the request in the timeout period (5 minutes)
    System.out.println("user never replied.");
}
from launchkey.exceptions import RequestTimedOut
from launchkey.entities.service import AuthResponseType, AuthResponseReason
from time import sleep
import logging

logger = logging.getLogger(__name__)

username = "myuser"
auth = service_client.authorization_request(username)
authorization_request_id = auth.auth_request

response = None
try:
    while response is None:
        sleep(1)
        response = service_client.get_authorization_response(authorization_request_id)
        if response is not None:
            if response.type == AuthResponseType.AUTHORIZED:
                # User logged in
                logging.info("Logged in")
            elif response.type == AuthResponseType.DENIED:
                if response.reason == AuthResponseReason.FRAUDULENT:
                    # User flagged request as fraudulent
                    logging.info("Fraud")
                # Regardless of fraud or not, it's still a denial so handle that as well
                logging.info("Denied due to denial id: %s" % response.denial_reason)
            elif response.type == AuthResponseType.FAILED:
                # The auth request failed due to one of the documented failure types. They can be accessed
                # and handled individually, but let's log the reason.
                logging.error("User auth failed due to: %s" % response.reason.value)
                # Handle failure
            else:
                # Unknown type
                logging.error("Unknown response type")
except RequestTimedOut:
    # The user did not respond to the request in the timeout period (5 minutes)

Starting a User Session

The Session Start method can execute with a username and an optional authorization request ID to start a user Session. An End User Session will show up in the User’s Authenticator to let the user know of an existing session. This will allow the User to end the session remotely.

var authorizationRequestId = "b1d05c28-0b18-41e4-94a0-853758eeefc8";
var username = "myuser";
serviceClient.SessionStart(username, authorizationRequestId);
String authorizationRequestId = "b1d05c28-0b18-41e4-94a0-853758eeefc8";
String username = "myuser";
serviceClient.sessionStart(username, authorizationRequestId);
authorization_request_id = "b1d05c28-0b18-41e4-94a0-853758eeefc8"
username = "myuser"
service_client.session_start(username, authorization_request_id)

Ending a User Session

The Session End method can execute with a username to end a user Session. This method should be called whenever a Service ends the End User’s session. This will be reflected in the End User’s Authenticator.

var username = "myuser";
serviceClient.SessionEnd(username);
String username = "myuser";
serviceClient.sessionEnd(username);
username = "myuser"
service_client.session_end(username)

Process Webhooks

Webhooks allow a service to reduce its load by not performing polling against an external API. An endpoint must be created to receive the webhook HTTP request and update the Service configuration accordingly. Here is a link to the setup instructions: Webhooks.

Webhooks are HTTP POST requests utilizing a JSON Web Token (JWT) for authorization and validation and a JSON Web Encrypted payload. To process a webhook, collect the request headers as a map or dictionary (with the key being a string and the value being a list of strings) and pass it with the request body as a string to the Handle Webhook method.

var context = listener.GetContext();
using (var reader = new StreamReader(context.Request.InputStream, Encoding.UTF8))
{
    var body = reader.ReadToEnd();
    var headers = new Dictionary<string, List<string>>();
    foreach (var headerName in context.Request.Headers.AllKeys)
    {
        headers.Add(headerName, new List<string>());
        foreach (var headerValue in context.Request.Headers.GetValues(headerName))
        {
            headers[headerName].Add(headerValue);
        }
    }
    Var webhookPackage = serviceClient.HandleWebhook(
            headers, body, context.Request.HttpMethod, context.Request.Url.AbsolutePath);
    if (webhookPackage is AuthorizationResponseWebhookPackage)
    {
        // Handle authorization response
    }
    else if (webhookPackage is ServiceUserSessionEndWebhookPackage)
    {
        // Handle session end
    }
}
// Spring Web Example
@RequestMapping(value = "/webhook", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void webhook (WebRequest request, @RequestBody String body) throws BaseException {
    Map<String, List<String>> headers = new HashMap<>();
    Iterator<String> headerNames = request.getHeaderNames();
    while (headerNames.hasNext()) {
        String headerName = headerNames.next();
        headers.put(headerName, Arrays.asList(request.getHeaderValues(headerName)));
    }
    WebhookPackage webhookPackage = serviceClient.handleWebhook(headers, body, "POST", "/webhook");
    if (webhookPackage instanceof AuthorizationResponseWebhookPackage) {
        // Handle authorization response
    } else if (webhookPackage instanceof ServiceUserSessionEndWebhookPackage) {
        // Handle session end
    }
}
# Flask example
# Path defined in your Service Callback URL value
from flask import Flask, request
from launchkey.entities.service import AuthorizationResponse, SessionEndRequest

app = Flask(__name__)

# Path defined in your Service Callback URL value
@app.route('/webhook', methods = ['POST'])
def launchkey_webhook():
    package = service_client.handle_webhook(request.data, request.headers, request.method, request.path)
    if isinstance(package, AuthorizationResponse):
        if package.authorized is True:
            # User accepted the auth, now create a session
            service_client.session_start(user, auth_request_id)
        else:
            # User denied the auth
    elif isinstance(package, SessionEndRequest):
        # The package will have the user hash, so use it to log the user out based on however you are handling it
        logout_user_from_my_app(package.service_user_hash)

Authorization Response Webhook

The Authorization Response webhook package will contain the same data that can be retrieved from polling. See the above table for the list of attributes.

Service User Session End Webhook

If a User ends one or all of their sessions from a linked Device or the Admin Center or a directory requests all sessions for a user to be ended, the service user session end webhook will be triggered.

Retrieving the service user hash from the Session Ended webhoook package allows you to identify the session(s) within your implementation that were initiated by a particular User. Use that value to end the session in your system.

The Session Ended webhook contains the following attributes:

API Time:The date and time the logout was performed.
Service User Hash:Hashed user identifier that will match the same value returned by an Authorization Response.

User Contributed

LaunchKey links to user contributed code as a resource to its community. LaunchKey does not in any way guarantee or warrant the quality and security of these code bases. User contributed code is supported by the creators. If you do find a link from the site to user contributed code that is malicious or inappropriate in any way, please report that link to LaunchKey immediately and we will investigate the claim. Submit any issue to LaunchKey support at https://launchkey.com./support. ×