In this post, I will explain why traditional authentication flows cannot accomodate modern technology sectors such as online banking, financial technology, and customer service. This will propose a solution to use a new authentication flow called Client Initiated Backchannel Authentication (CIBA). I by no means put down any authentication flows available in the industry, but rather explain why CIBA is a great fit in its own context. It is a matter of tradeoff.
This post starts out firstly by giving a small introduction about what the current options for authentication and authorization are, secondly the problem that cannot be solved using traditional authentication flows, last but not least the solution that CIBA provides.
A basic understanding of OAuth 2.0 and OpenID Connect (OIDC) is recommended to follow through this post.
Many developers are accustomed to the authorization framework called OAuth 2.0 which allows third party apps to be given permission access on behalf of an end-user. OAuth 2.0 is equipped with grant types or flows which defines the steps of how the authorization is performed. The goal is to get an access token which can be used to access protected resource.
While OAuth 2.0 provides authorization, many misuse it for authentication. To counter this, an extended framework has been established by the name of OpenID Connect (OIDC). OIDC is built on top of OAuth 2.0. OIDC allows Client Applications to verify the identity of an end-user based on the authentication performed by an Authorization Server. OIDC has a new concept called ID Token. ID Token is formatted as JSON Web Token (JWT), and is assymetrically signed. An ID Token can be used as an identity card which defines facts of an end-user. This might be in the forms of:
- user identifier
- phone number
- the list goes on...
OIDC provides similar grants as OAuth 2.0. The grants are:
These OIDC grants are based on traditional redirection mechanisms.
I will elaborate some jargons that are used in this post.
|Authorization Server||This entity provides authentication and authorization service. It's also known as OpenID Provider (OP), it can be used interchangeably.|
|Client Application||This entity is the application that consumes the Authorization Server. It's also known as Relying Party (RP), it can be used interchangeably.|
|Consumption Device||This entity is the machine, device or user agent that runs the Client Application. It's also known as CD.|
|Authentication Device||This entity is the machine or device that is used to allow access to a client application. Commonly a smartphone, and in the hands of the end-user. It's also known as AD.|
In Authorization Code, Implicit and Hybrid grant, the process is done throught redirects. When a client application needs to be authenticated:
- it will be redirected to the Authorization Server's login page
- authenticate the end-user through the use of user & password, biometrics etc (deployment specific)
- once the authentication is successful, it will be redirected back to the Client Application's page
- continue to fetch access token (true for the case of Authorization Code)
A redirection is needed because Authorization Server doesn't have direct communication with Client Application. The authentication process is also initiated by the end-user's interaction on the Consumption Device. Hence it makes sense to say that the Authentication Device and Consumption Device are the same machines.
See Figure 1 for the redirection scheme.
- A Client Application that needs authentication will communicate with the Authorization Server, and be redirected to the Authorization Server login page.
- Here the end-user will provide credentials. Once the authentication is done, the end-user will be redirected back to the Client Application page. This will include an authorization code in the case of Authorization Code grant.
- If this is an implicit grant, it will receive the token straight away. But in instances of Authorization Code grant, it will need to request for tokens.
- The Client Application will receive the tokens.
With the evolvement of modern technology, these traditional flows can't fulfill certain use cases. So which use cases fall into this category?
- online banking & financial technology: there are devices that can be used to pay a consumer's shopping, a payment kiosk, point of sales, a smart cashier etc. The authentication flows have user experience that allows consumer to continue the payment process by acknowledging it on their smartphone.
- customer sevice: customer care representatives in a remote center can access data of a consumer only if they are given access to it. A user then must allow the data to be shared to the customer representative, which can be done through a smartphone.
Through observing the mentioned use cases, they both require authentication through a separate device (smartphone). Hence, traditional flows can't be applied, as redirection only works on the same device.
CIBA allows a Client Application that knows the identifier of an end-user, to start an authentication flow, and be given consent on a seperate device, called an Authentication Device. This authentication flow is applicable for use cases where a redirection authentication scheme will not work, as mentioned previously.
CIBA defines 3 modes to request tokens, they are poll, ping, and push. This is different compared to the traditional flows, where they only define 1.
The next subsection will give a high level overview of CIBA's mechanism.
Steps in CIBA
This step is merely a configuration of the Client Application and Authorization Server in the CIBA environment. For the Client Application, it has to be registered in the Authorization Server. Just like OAuth 2.0, it needs to have client_id, client_secret etc. For it to be applicable in CIBA, the Client Application must have these metadatas:
- bakchannel_token_delivery_mode: [required] this is the value of which token mode the client is using. It must be 1 of the 3 between poll, ping, and push. The token mode chosen must be supported by the Authorization Server. If a Client Application uses push mode, but the Authorization Server only supports ping and push, then it must not continue the authentication request.
- backchannel_client_notification_endpoint: [required] this is the value of the URL used by the Authorization Server to send notification to Client Application. In push and ping mode, the Authorization Server will send a message to the Client Application to notify that a token is ready to be requested (ping), and send token (push).
- backchannel_authentication_request_signing_alg: [optional] this is the algorithm the Client Application uses to send signed authentication request. e.g. RS256, RS384 etc.
- backchannel_user_code_parameter: [optional] this is a flag to show that the Client Application supports user_code parameter.
The Authorization Server must have these metadatas:
- backchannel_token_delivery_modes_supported: [required] the token modes it supports. It must contain atleast 1 out of poll, ping, and push.
- bakchannel_authentication_endpoint: [required] this is the value of the URL used to start authentication.
- backchannel_authentication_request_signing_alg_values_supported: [optional] the algorithms the server supports for signed authentication requests.
- backchannel_user_code_parameter_supported: [optional] this is a flag to show that the server supports use_code parameter.
You're wondering what these metadatas are for, when to use it, how to code it etc. They are used to register the Client Application and Authorization Server. OIDC defines a concept called dynamic registration, where an endpoint is used to register a Client Application. Those metadatas can be used as parameters. Alternatively, if dynamic registration isn't supported, and an arbitrary solution exists e.g. manually entering through database, then those parameters can be modelled as columns in the database table. e.g. there exists a oauth_client table that represents Client Application registered to use OAuth 2.0 and OIDC, the metadatas can be added as columns, such as backchannel_token_delivery_mode etc.
Backchannel Authentication Request
Once registration has been done, a Client Application can then start making authentication request.
This step is where the CIBA flow begins. A Client Application sends an authentication request to the Authorization Server. To do so, there must be a trigger for it to start.
In circumstances of customer service use case, the operator can input the consumer's email, phone number, account number etc. as a form of identifier. In instances of no operator (other than the end-user) operating the Client Application, the Consumption Device (device that runs the Client Application) can scan a QR code on the end-user's device, or end-user can manually enter their phone number (or any other identifier). This identifier is called login_hint.
Besides that, the Client Application will state which resources it accesses. It's called scope. If the Client Application needs write access to an end-user's account balance, then there might be a scope called "payment.balance.write", or if a customer service needs an end-user's address, there might be a scope called "address". You get the point, these scopes are usually defined by the Authorization Server, and is deployment specific. There are certain scopes in OIDC that are already standards, which you can read in the specification.
Those 2 datas are required to the bare minimum to start an authentication flow. There are other parameters such as binding_message, user_code etc which I will explain in another post (stay tuned).
The Authorization Server will need to validate the authentication request. This includes:
- authenticating the Client Application through its registered method (HTTP Basic, Client Secret JWT, MTLS etc)
- validate if the request is signed, make sure the signature is valid
- validate all the required parameters are present
- make sure the user's identifier is valid
An error will occur if a violation occurs in the validation. You can read the full validation rules here.
Backchannel Authentication Response
After a successful validation, the Client Application will receive a response in the form of:
|auth_req_id||This is the identifier that represents the authentication session. It will be used for giving consent and token requests. e.g. "ff618995-1dd7-42b4-9134-9c713bc3d035"|
|expires_in||When the auth_req_id will expire.|
|interval||The minimal number in seconds a Client Application should poll the token endpoint (only applicable in poll mode).|
Client Application should persist auth_req_id until the CIBA flow ends.
Once the Client Application has an authentication session, the Authorization Server will send a message to the Authentication Device asking for consent. The mechanism used to send a message can leverage existing solutions such as push notifications, firebase, pub/ sub etc. It is deployment specific.
It has to be noted though, when the Authorization Server sends the message, it needs to include:
- auth_req_id: so that when the end-user grants consent, it will target the right authentication session.
- binding_message: to guarantee that the Consumption Device/ Client Application and Authentication Device are on the same session.
The specification doesn't mention the implementation of giving end-user consent, but as of my experience, those 2 datas need to be sent.
Once it is sent, the end-user will receive a form of dialog. e.g. "Point of Sales Bank of ABC needs permission to continue payment: [Continue] or [Cancel]".
The end-user can choose either to give consent, by clicking Continue, or reject, by clicking Cancel. The Authentication Device will send this consent to the Authorization Server, along with auth_req_id.
The request should be protected with authorization mechanism. The app on the Authentication Device must be authenticated before the consent can start, which means there must be a form of login. You can use Authorization Code with PKCE on this mobile app. The access token received can be used as authorization header for the consent request.
The specification doesn't mention this so it's deployment specific.
CIBA defines 3 modes.
In poll mode, the Client Application can start sending token requests once it receives auth_req_id. The requests can be done at an interval stated in the authentication response. So if the interval was 5 seconds, then it can poll every 5 seconds. If it polls too fast, let's say every 3 seconds, then the Authorization Server sends a slow down response.
The Client Application keeps on polling until the end-user gives consent (allow or deny). If the consent was allowed, then the token will be given. On the contratry, if the consent was denied, the Client Application won't get the token.
In ping mode, the Client Application will get a notification from the Authorization Server once the end-user gives a consent (allow or deny).
The notification is sent through the endpoint registered by the Client Application (backchannel_client_notification_endpoint, mentioned here.
The notification request is a mechanism to tell the Client Application, that the token is ready to retrieve. In the notification request, the auth_req_id is sent, along with an authorization bearer which is the client_notification_token, a token generated by the Client Application. This token is to ensure that when Authorization Server sends a notification, the Client Application can verify that it is from a trusted source, hence the client_notification_token, stored in the authorization header, must be checked. Therefore the Client Application needs to temporarily persist it. Once it receives the notification, the Client Application requests the token through the token endpoint, which can be the same as the endpoint used in poll mode.
In push mode, the Client Application doesn't request the tokens, but the Authorization Serve sends it directly, hence the name "push". This has similar mechanism with ping mode, such that it is sent through the backchannel_client_notification_endpoint with the use of client_notification_token.
Moreover, there is an extra mechanism that the Client Application needs to perform. It has to verify claims "at_hash" and "urn:openid:params:jwt:claim:auth_req_id" which are the value of the access token hash, and the auth_req_id. The claims are stored in the ID Token.
Once the token response is successful, the Client Application can continue its business process. Such as making a payment, accessing a consumer's sensitive data etc. The access token is used as authorization header to hit protected endpoints.
CIBA can be used when the Consumption Device and Authentication Device are separated physically. It can be in great consideration to use this flow, as it provides a pleasent user experience, by not having redirects.
I know that this post is lacking details such as, how to construct each request, how to validate the ID Token claims, the threat model, comparisons with other decoupled flows (Device Authorization grant) and many more. I will gladly put in some time for another post to discuss further details missing. This post is meant to be a high level overview of CIBA.
I plan to give my story of how I implemented this protocol (hope that I have time to write).
I hope you can learn alot from this post and I am seeking feedbacks from readers 🙂
References & Further Readings
- https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0-ID1.html (draft 03)