User Authentication with CIBA
To authenticate a user, Client-Initiated Backchannel Authentication (CIBA) does not rely on a client application redirecting the user via the browser to perform the login/authentication process. Instead, the client application directly calls the OpenID Provider via a backchannel request to initiate the authentication flow.
The following sequence diagram illustrates an implementation of the CIBA Flow:
The sequence diagram defines two actors: an authorizing user and an initiating user. The authorizing and initiating user can be two different people, such as a call center caller and a call center agent. In other use cases, they can be the same person, such as a user authenticating to a retail kiosk or another connected device.
The following sections dive step-by-step into how user authentication works with the CIBA Flow:
Step 6: Mobile application presents the consent details to the user
Step 7: Mobile application sends the user response back to Auth0
Step 8: Auth0 receives user response after the flow completes
Prerequisites
To initiate a CIBA push request, the authorizing user must be enrolled in MFA using push notifications. To verify in the Auth0 Dashboard, navigate to User Management > Users and click on the user:
If you have set Multi-factor Authentication as always required for your tenant, users are prompted to enroll for MFA at their next login. You can also use Actions to prompt for MFA enrollment.
MFA push notifications is usually implemented within a custom mobile app that embeds the Guardian SDK. To learn more, read Configure Client-Initiated Backchannel Authentication.
Step 1: Client application initiates a CIBA request
Use the User Search APIs to find the authorizing user for whom you’d like to initiate a CIBA request and obtain their user ID.
Once you have a user ID for the authorizing user, use the Authentication API to send a POST request to the /bc-authorize
endpoint:
curl --location 'https://[TENANT].auth0.com/bc-authorize' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=[CLIENT ID]' \
--data-urlencode 'client_secret=[CLIENT SECRET]' \
--data-urlencode 'login_hint={ "format": "iss_sub", "iss": "https://[TENANT].auth0.com/", "sub": "auth0|[USER ID]" }' \
--data-urlencode 'scope=[SCOPE]' \
--data-urlencode 'binding_message=[BINDING MESSAGE]'
Was this helpful?
Parameters | Description |
---|---|
TENANT | Tenant name. It can also be a custom domain. |
CLIENT ID | Client application identifier |
CLIENT SECRET | Client authentication method used for user authentication with CIBA, such as Client Secret, Private Key JWT, or mTLS Authentication. |
SCOPE | Must include openid .The scope can optionally include offline_access to request a refresh token. However, for one-time authorization of a transaction with the CIBA Flow, a refresh token is not needed and does not have any meaning in this context. |
USER ID | User ID for the authorizing user that is passed within the login_hint structure.The user ID for a federated connection may have a different format. |
EXPIRY | The CIBA flow's requested expiry is between 1 and 300 seconds, and it defaults to 300 seconds. |
BINDING MESSAGE | Message used to bind the CIBA flow across the authentication and consumption devices. The binding message is required and up to 64 characters. Use only alphanumeric and +-_.,:# characters |
AUDIENCE | Unique identifier of the audience for an issued token. |
Step 2: Auth0 tenant acknowledges the CIBA request
If the Auth0 tenant successfully receives the POST request, you should receive a response containing an auth-req-id
that references the request:
{
"auth_req_id": "eyJh...",
"expires_in": 300,
"interval": 5
}
Was this helpful?
The auth_req_id
value is passed to the /token
endpoint to poll for the completion of the CIBA flow.
Step 3: Client application polls for a response
Use the Authentication API to call the /token
endpoint using the urn:openid:params:grant-type:ciba
grant type and the auth_req_id
you received from the /bc-authorize
endpoint:
curl --location 'https://[TENANT].auth0.com/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=[CLIENT ID]' \
--data-urlencode 'client_secret=[CLIENT SECRET]' \
--data-urlencode 'auth_req_id=[FROM THE BC-AUTHORIZE RESPONSE]' \
--data-urlencode 'grant_type=urn:openid:params:grant-type:ciba'
Was this helpful?
Until the authorizing user approves the transaction, you should receive the following response:
{
"error": "authorization_pending",
"error_description": "The end-user authorization is pending"
}
Was this helpful?
There is approximately a five-second wait interval for polling. If you poll too frequently, you will receive the following response, where the description varies depending on the backoff interval:
{
"error": "slow_down",
"error_description": "You are polling faster than allowed. Try again in 10 seconds."
"interval": 10
}
Was this helpful?
To resolve the error, wait until the next interval (in seconds) to poll the /token
endpoint.
Step 4: Mobile application receives the push notification
Auth0 sends a push notification to the user's registered mobile app or device. The Guardian SDK provides methods to parse the data received from the push notification and return a ready-to-use Notification
instance. The Notification
instance includes a transaction linking ID, or txlinkid
, that the mobile application uses to retrieve the consent details from Auth0.
The following code samples are example iOS and Android mobile push notification implementations using the Guardian SDK:
//implementing UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
if let notification = Guardian.notification(from: userInfo) {
handleGuardianNotification(notification: notification)
}
}
Was this helpful?
// at the FCM listener you receive a RemoteMessage
@Override
public void onMessageReceived(RemoteMessage message) {
Notification notification = Guardian.parseNotification(message.getData());
if (notification != null) {
// you received a Guardian notification, handle it
handleGuardianNotification(notification);
return;
}
/* handle other push notifications you might be using ... */
}
Was this helpful?
Step 5: Mobile application retrieves the consent details
Call the Guardian SDK from your mobile application to retrieve the consent details i.e. the contents of the binding_message
from the Auth0 Consent API.
The following code samples are example iOS and Android implementations that retrieve data from the Auth0 Consent API:
let device: AuthenticationDevice = // the object you obtained when enrolling
if let consentId = notification.transactionLinkingId {
Guardian
.consent(forDomain: {yourGuardianDomain}, device: device)
.fetch(consentId: consentId, notificationToken: notification.transactionToken)
.start{result in
switch result {
case .success(let payload):
// present consent details to the user
case .failure(let cause):
// something went wrong
}
}
}
Was this helpful?
Enrollment enrollment = // the object you obtained when enrolling
if (notification.getTransactionLinkingId() != null) {
guardian
.fetchConsent(notification, enrollment)
.start(new Callback<Enrollment> {
@Override
void onSuccess(RichConsent consentDetails) {
// present consent details to the user
}
@Override
void onFailure(Throwable exception) {
// something went wrong
}
});
}
Was this helpful?
Step 6: Mobile application presents the consent details to the user
The Auth0 Consent API sends a response that contains the binding_message
, or the consent details, to the mobile application. The mobile application presents the authentication request and/or the consent details to the user.
The following code sample is an example response from the Auth0 Consent API:
{
id: string,
requestedDetails: {
audience: string,
scope: string,
binding_message: string
},
created_at: unix timestamp
expires_at: unix timestamp
}
Was this helpful?
The user can accept or decline the authentication request at this point.
Step 7: Mobile application sends the user response back to Auth0
Depending on whether the user accepts or rejects the authentication request, the mobile application sends the user response back to Auth0.
The following code samples are example iOS and Android implementations that handle the user response:
User accepts the authentication request
Guardian
.authentication(forDomain: "{yourGuardianDomain}", device: device)
.reject(notification: notification)
// or reject(notification: notification, withReason: "hacked")
.start { result in
switch result {
case .success:
// the auth request was successfully rejected
case .failure(let cause):
// something failed, check cause to see what went wrong
}
}
Was this helpful?
guardian
.allow(notification, enrollment)
.execute(); // or start(new Callback<> ...)
Was this helpful?
User rejects the authentication request
Guardian
.authentication(forDomain: "{yourGuardianDomain}", device: device)
.reject(notification: notification)
// or reject(notification: notification, withReason: "hacked")
.start { result in
switch result {
case .success:
// the auth request was successfully rejected
case .failure(let cause):
// something failed, check cause to see what went wrong
}
}
Was this helpful?
guardian
.reject(notification, enrollment) // or reject(notification, enrollment, reason)
.execute(); // or start(new Callback<> ...)
Was this helpful?
Step 8: Auth0 receives user response after the flow completes
The client application completes the polling upon receiving a response from the /token
endpoint. A CIBA flow always requires a response, either an approval or decline, from the authorizing user, and existing grants are not checked.
If the user rejects the push request, you should receive the following response:
{
"error": "access_denied",
"error_description": "The end-user denied the authorization request or it has been expired"
}
Was this helpful?
If the user approves the push request, you should receive the following response:
{
"access_token": "eyJh...",
"id_token": "eyJh...",
"expires_in": 86400,
"scope": "openid"
}
Was this helpful?