CIBA
The Client-Initiated Back-channel Authentication (CIBA) flow lets people use their mobile device to authenticate and approve transactions in their everyday life, be it at a PoS, at a counter or in a phone call with a service agent.
In contrast to the common OAuth flows, such as the authorization_code flow,
the client requesting the tokens and the application where the user gets
authenticated and consents the transaction reside on separate devices in the
CIBA flow. The CIBA client thus cannot use a front-channel redirection to the
IdP server. Instead, the CIBA client needs to make a back-channel request to
the IdP server, which will then invoke the instance of the IdP’s user authN /
consent application installed on the user’s device. In order for the IdP server
to identify which mobile app instance needs to be invoked, the client is
expected to include a hint, as either login_hint_token
, id_token_hint
or
login_hint
in the CIBA request. At a PoS terminal or counter this can be
special token generated by the client and passed to the client via NFC. In a
call centre scenario this can be the caller ID. Depending on the security
context the client may also need to include a user_code
, to prevent
unsolicited CIBA requests from popping up on a user’s device.
CIBA was originally designed as an OpenID Connect extension, but it can also be used in plain OAuth 2.0 scenarios where only authorisation is needed.
Support for CIBA was added in version 8.31 of the SDK.
CIBA client registration
Example registration for a CIBA client where the tokens are going to be
delivered via the
push
mode, with an HTTP POST to the client, after the back-channel authorisation is
complete. The client authenticates with the self_signed_tls_client_auth
(mTLS) method, which enables issue of client-certificate bound access tokens.
import java.net.*;
import java.util.*;
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.auth.*;
import com.nimbusds.oauth2.sdk.ciba.*;
import com.nimbusds.oauth2.sdk.client.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.openid.connect.sdk.rp.*;
// The OP / AS client registration endpoint
URI clientRegEndpoint = URI.create("https://demo.c2id.com/clients/");
// The initial registration access token
BearerAccessToken clientRegToken = new BearerAccessToken("...");
// Prepare the client metadata for CIBA with push token delivery,
// the client is going to authenticate with a self-signed certificate (mTLS)
OIDCClientMetadata clientMetadata = new OIDCClientMetadata();
clientMetadata.setGrantTypes(Collections.singleton(GrantType.CIBA));
clientMetadata.setJWKSetURI(URI.create("https://client.example.com/jwks.json"));
clientMetadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH);
clientMetadata.setBackChannelTokenDeliveryMode(BackChannelTokenDeliveryMode.PUSH);
clientMetadata.setBackChannelClientNotificationEndpoint(URI.create("https://client.example.com/ciba"));
clientMetadata.setSupportsBackChannelUserCodeParam(true);
clientMetadata.setScope(new Scope("openid", "email", "profile", "phone"));
// Send the registration request
HTTPResponse httpResponse = new OIDCClientRegistrationRequest(
clientRegEndpoint, clientMetadata, clientRegToken)
.toHTTPRequest()
.send();
ClientRegistrationResponse regResponse = OIDCClientRegistrationResponseParser.parse(httpResponse);
if (! regResponse.indicatesSuccess()) {
// Registration failed
System.err.println(regResponse.toErrorResponse().getErrorObject());
return;
}
// Successful registration
OIDCClientInformation clientInfo = (OIDCClientInformation) regResponse.toSuccessResponse().getClientInformation();
System.out.println("Client ID: " + clientInfo.getID());
System.out.println("Client registration token: " + clientInfo.getRegistrationAccessToken());
System.out.println("Client metadata: " + clientInfo.getOIDCMetadata());
CIBA request
Example CIBA request for an ID token. The identity of the user is hinted by
means of a caller ID, which can occur in scenarios where a service agent needs
to authenticate the person who is calling in. A user_code
to prevent
unsolicited CIBA requests it not needed since the agent is typically
authenticated and trusted by the client software.
import java.net.*;
import java.util.*;
import javax.net.ssl.*;
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.auth.*;
import com.nimbusds.oauth2.sdk.ciba.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.openid.connect.sdk.*;
import com.nimbusds.openid.connect.sdk.rp.*;
// The OP / AS endpoint for back-channel authN and authZ requests
URI cibaEndpoint = URI.create("https://demo.c2id.com/ciba");
// Custom SSL factory for mTLS with the client's certificate
SSLSocketFactory sslSocketFactory = ...;
ClientAuthentication clientAuth = new SelfSignedTLSClientAuthentication(clientInfo.getID(), sslSocketFactory);
// Generate a bearer token to authorise the callback with the token delivery
// at the client notification endpoint
BearerAccessToken clientNotifyToken = new BearerAccessToken();
// Make a CIBA request for an ID token, using a caller ID as login hint
HTTPResponse httpResponse1 = new CIBARequest.Builder(
clientAuth,
new Scope(OIDCScopeValue.OPENID))
.endpointURI(cibaEndpoint)
.clientNotificationToken(clientNotifyToken)
.loginHint("+1-541-754-3010")
.build()
.toHTTPRequest()
.send();
CIBAResponse cibaResponse = CIBAResponse.parse(httpResponse);
if (! cibaResponse.indicatesSuccess()) {
// CIBA request failed
System.err.println(cibaResponse.toErrorResponse().getErrorObject());
return;
}
// Get the request acknowledgement
CIBARequestAcknowledgement acknowledgement = cibaResponse.toRequestAcknowledgement();
AuthRequestID cibaRequestID = acknowledgement.getAuthRequestID();
int expiresInSeconds = acknowledgement.getExpiresIn();
// Store the request context (with the client callback token
// and other necessary details), keyed by the auth_req_id and
// set to expire according to the received expires_in value in
// seconds
// ...
Processing callbacks
Example code for processing a callback at the client notification endpoint,
assuming the client is registered for the push token delivery method. The IdP
is going to POST the issued tokens (or an error message if authentication
failed) directly to the client. The client uses the auth_req_id
to retrieve
the original request context.
import com.nimbusds.jwt.*;
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.auth.*;
import com.nimbusds.oauth2.sdk.ciba.*;
import com.nimbusds.oauth2.sdk.http.*;
import com.nimbusds.oauth2.sdk.token.*;
import com.nimbusds.openid.connect.sdk.*;
import com.nimbusds.openid.connect.sdk.rp.*;
// Get the HTTP request at the client notification endpoint endpoint
HTTPRequest httpRequest = ...;
// Parse the callback
CIBATokenDelivery tokenDelivery = CIBATokenDelivery.parse(httpRequest);
cibaRequestID = acknowledgement.getAuthRequestID();
// Get the request context previously stored by auth_req_id, if expired abort
// ...
// Verify the callback access token with the stored one
if (! clientNotifyToken.equals(tokenDelivery.getAccessToken())) {
System.err.println("Invalid access token");
return;
}
if (! tokenDelivery.indicatesSuccess()) {
// The IdP posted an error
System.err.println(tokenDelivery.toErrorDelivery().getErrorObject());
return;
}
// Get the delivered token(s)
CIBATokenDelivery successfulTokenDelivery = tokenDelivery.toTokenDelivery();
JWT idToken = successfulTokenDelivery.getOIDCTokens().getIDToken();
// Verify the ID token
// ...