In most cases, a user authentication is started from a web-service by scanning a QR Code or following a Deep-Link. This however is not possible, if the app integrates the service directly, e.g. if it requires an authentication before it can display sensible information. For that purpose the XignSys SDK offers a process which is called InApp-Authentication.
During this process all steps a regular authentication consists of (with respect to the SDK and the service side) are performed, including fetching the process initialization data (the content of the QR Code or Deep-Link), completion of the SDK side part of the authentication as well as fetching the OIDC (OpenID Connect) authorization code. After the InApp-Authentication is finished and the authorization code has been retrieved, the app can be assured the user has been successfully authenticated.
More data about the user, e.g. the username, is not available for the app at this point. Additional information can be retrieved by fetching the user claims from the XignIn-Manager and providing the authorization code as specified in OIDC. Fetching the user claims however is not possible from within the SDK for security reasons. To fetch the user claims the app would need the private key of the service configuration to at least verify the signature of the JWT containing the user claims. Currently, there is no process to load these data securely after pushing an app—and putting these directly in the app package is not recommended because app packages can be unpacked/decompiled. Therefore, it is the responsibility of the app to securely transfer the authorization code to a backend system which then can fetch the user claims and deliver them back to the app.
The start of InApp-Authentication depends on the service configured within the XignIn-Manager. Currently, two scenarios are possible:
In the first scenario an InApp-Authentication is started directly against a service configured in the XignIn-Manager. This should be used if the XignIn-Manager is set up as Identity Provider, meaning the user data are stored within the manager itself. The second scenario involves a setup where the user data are stored within a Keycloak and the service configured in the XignIn-Manager is only used to authenticate against the Keycloak. In this case an InApp-Authentication is started against the service configured in the Keycloak.
In order to use the direct InApp-Authentication the SDK must have been initialized with the configuration of the service to authenticate against. Please refer to the chapter Service configurations for that matter. The process can be started as follows:
private func startDirectInAuthentication() throws {
let authenticator: Authenticator = XignSdk.shared.authenticator
// Create an `InAppAuthenticationTrigger` with the clientId from the clients configuration.
let trigger: InAppAuthenticationTrigger =
.direct(clientId: "0a6e73d6-30bc-4864-b88e-b274582313c5")
// Fetch the authentication initialization data with the previously created trigger.
let aid: AuthenticationInitializationDataInApp = try authenticator
.fetchInAppAuthenticationInitializationDataSynchronous(trigger)
// Start the authentication process with the 'aid'.
// Note: This processes is simplified by invoking the example function from the chapter
// **Starting an authentication** XignSys SDK documentation.
try startAuthentication(authenticationInitializationData: aid)
}
private fun startDirectInAuthentication() {
val authenticator: Authenticator = XignSdk.shared.authenticator
// Create an `InAppAuthenticationTrigger` with the clientId from the clients configuration.
val trigger: InAppAuthenticationTrigger =
InAppAuthenticationTrigger.Direct("0a6e73d6-30bc-4864-b88e-b274582313c5")
// Fetch the authentication initialization data with the previously created trigger.
val aid: AuthenticationInitializationDataInApp = authenticator
.fetchInAppAuthenticationInitializationDataSynchronous(trigger)
// Start the authentication process with the 'aid'.
// Note: This processes is simplified by invoking the example function from the chapter
// **Starting an authentication** XignSys SDK documentation.
startAuthentication(authenticationInitializationData = aid)
}
The clientId of the service can either be copied from the "service-name".json configuration file or fetched from the
property of the SDK instance named serviceConfigurations. The result of the function is an instance
of AuthenticationInitializationDataInApp which can be used to start an authentication. For that purpose, please refer
to the chapter Starting an authentication.
In order to start an InApp-Authentication against a Keycloak service the service configuration must be provided i.e., the Keycloak service client ID, redirect URL, authorization endpoint path, realm and optionally the OIDC scopes. With this data the process can be started as follows:
Important: Starting with version 5.0.0 of the XignSys SDK the initializer of the
KeycloakServiceConfigurationhas been complimented with an additional parameter namedauthorizationEndpointPath. This was necessary, because since Keycloak version 18.0 the endpoint for the authorization has changed. Therefore, the authorization endpoint path has to be specified depending on the version of the Keycloak server in use.
private func startKeycloakInAuthentication() throws {
let authenticator: Authenticator = XignSdk.shared.authenticator
// Create an `InAppAuthenticationTrigger` with the Keycloak service configuration data.
let trigger: InAppAuthenticationTrigger = .keycloak(
KeycloakServiceConfiguration(
keycloakUrl: URL(string: "https://identity.xignin.dev/")!,
redirectUrl: URL(string: "https://identity.xignin.dev/realms/SDK-Playground/account/login-redirect")!,
// The authorization endpoint path depending on the version of the Keycloak server in use.
// If the Keycloak server version is less than 18.0 use "/auth/realms/..." else "/realms/...".
authorizationEndpointPath: "/realms/SDK-Playground/protocol/openid-connect/auth",
clientId: "account",
realm: "SDK-Playground",
// Optionally, a list of scopes of the Keycloak service which determine the type of
// data the user claims will contain. Note: The scope `openid` will be
// added automatically.
scopes: ["profile", "roles"]
)
)
// Fetch the authentication initialization data with the previously created trigger.
let aid: AuthenticationInitializationDataInApp = try authenticator
.fetchInAppAuthenticationInitializationDataSynchronous(trigger)
// Start the authentication process with the 'aid'.
// Note: This processes is simplified by invoking the example function from the chapter
// **Starting an authentication** XignSys SDK documentation.
try startAuthentication(authenticationInitializationData: aid)
}
private fun startKeycloakInAuthentication() {
val authenticator: Authenticator = XignSdk.shared.authenticator
// Create an `InAppAuthenticationTrigger` with the Keycloak service configuration data.
val trigger: InAppAuthenticationTrigger = InAppAuthenticationTrigger.KeyCloak(
KeycloakServiceConfiguration(
keycloakUrl = URL("https://identity.xignin.dev/"),
redirectUrl = URL("https://identity.xignin.dev/realms/SDK-Playground/account/login-redirect"),
// The authorization endpoint path depending on the version of the Keycloak server in use.
// If the Keycloak server version is less than 18.0 use "/auth/realms/..." else "/realms/...".
authorizationEndpointPath = "/realms/SDK-Playground/protocol/openid-connect/auth",
clientId = "account",
realm = "SDK-Playground",
// Optionally, a list of scopes of the Keycloak service which determine the type of
// data the user claims will contain. Note: The scope `openid` will be
// added automatically.
scopes = arrayOf("profile", "roles")
)
)
// Fetch the authentication initialization data with the previously created trigger.
val aid: AuthenticationInitializationDataInApp = authenticator
.fetchInAppAuthenticationInitializationDataSynchronous(trigger)
// Start the authentication process with the 'aid'.
// Note: This processes is simplified by invoking the example function from the chapter
// **Starting an authentication** XignSys SDK documentation.
startAuthentication(authenticationInitializationData = aid)
}
Like the direct start, this function results in an instance of AuthenticationInitializationDataInApp that can be used
to start an authentication. For that purpose, please also refer to the
chapter Starting an authentication.
The InApp-Authentication can be finished with a ServiceLoginResult acquired at the end of an authentication process as
discussed in the chapter Finish an authentication. With this object the
function Authenticator.fetchAuthorizationCodeSynchronous(:) can be invoked to fetch the authorization code. The result
of the function is an enum (a sealed class in Kotlin) of the type AuthorizationCodeResult. This enum has only one
case named final - the previous second case continuable has been removed entirely with XignSys version 5.0.0.
The case final has two additional (sub-)cases direct and keycloak. Depending on which service configuration was
used to start the InApp-Authentication the respective case can be expected. If a service of the XignIn-Manager was
directly used the case direct is returned with the parameters (properties) code - the OIDC authorization code -
and clientId - the client ID of the service. Otherwise, the case keycloak is returned, if a Keycloak configuration
was used with the parameters (properties) code - the OIDC authorization code - and redirectUrl - the redirect URL to
the service.
Either way, the returned OIDC authorization code could be used to fetch the user claims. As mentioned in the beginning of the chapter, this step must be implemented without the help of the XignSys SDK. It is recommended to transfer the authorization over a TLS secured (encrypted) connection to a backend system and fetch the user claim from this system if the user claims are required by a feature in the app. Such a backend system would need the client secrets (private key and/or symmetric secret) and the configuration of the service in order to connect to the correct endpoints and validate/decrypt the user claims.
The following illustration shows an example on how the OIDC authorization code could be retrieved and passed to a service specific implementation that handles fetching the user claims:
private func fetchUserClaims(serviceLoginResult: ServiceLoginResult) throws {
let authenticator: Authenticator = XignSdk.shared.authenticator
// Fetch the OIDC authorization code with the `ServiceLoginResult` from the end of
// a successful authentication process.
let result: AuthorizationCodeResult = try authenticator
.fetchAuthorizationCodeSynchronous(serviceLoginResult)
switch result {
case .final(let data):
switch data {
case .direct(code: let code, clientId: let clientId):
// Fetch the user claims independently with the given OIDC authorization code.
YourImplementation.fetchUserClaims(oidcAuthorizationCode: code)
case .keycloak(code: let code, redirectUrl: let redirectUrl):
// Fetch the user claims independently with the given OIDC authorization code.
YourImplementation.fetchUserClaims(oidcAuthorizationCode: code)
}
}
}
private fun fetchUserClaims(serviceLoginResult: ServiceLoginResult) {
val authenticator: Authenticator = XignSdk.shared.authenticator
// Fetch the OIDC authorization code with the `ServiceLoginResult` from the end of
// a successful authentication process.
val result: AuthorizationCodeResult = authenticator
.fetchAuthorizationCodeSynchronous(serviceLoginResult)
when (result) {
is AuthorizationCodeResult.Final.Direct -> {
val code: String = result.code
val clientId: String = result.clientId
// Fetch the user claims independently with the given OIDC authorization code.
YourImplementation.fetchUserClaims(oidcAuthorizationCode = code)
}
is AuthorizationCodeResult.Final.Keycloak -> {
val code: String = result.code
val redirectUrl: URL = result.redirectUrl
// Fetch the user claims independently with the given OIDC authorization code.
YourImplementation.fetchUserClaims(oidcAuthorizationCode = code)
}
}
}