Skip to Content

What is CIBA?

Traditionally, users access client applications on the same device they authenticate with OpenID Connect providers, usually via redirects in a web application.

These traditional flows are now known as coupled authentication flows, wherein a user initiates an authentication request and authenticates on the same device they're accessing the client application on.

CIBA introduces the idea of decoupled flows wherein the user's authentication device is decoupled from the client application. Moreover, decoupled flows allow for the client application to initiate the authentication request.

For example, a call centre employee may be talking to a customer over the phone and may need them to authenticate themselves before proceeding. Normally, this process would be done via the answering of some security questions over the phone. However, using CIBA, the call centre employee just needs to initiate a CIBA authentication request on behalf of the customer. The customer on the other end of the line may then get a text or push notification asking them to confirm their identity. Assuming successful authentication, the call centre employee can proceed with the call.

Note: The CIBA flow can only be used for confidential clients 

Diagram showing the CIBA authentication flow

Backchannel authentication request endpoint

We will use the sample Duende IdentityServer with CIBA user interaction pages as a starting point.

The new backchannel authentication request endpoint is the first new addition to your IdentityServer you'll find when working with the CIBA flow.

You can view the value for the new endpoint by querying your IdentityServer's Discovery Document.

The backchannel_authentication_endpoint is the endpoint that our client application will call to initiate a CIBA request. The endpoint is protected in the same way as the token endpoint; meaning you'll need to authenticate your client against it using any authentication method configured for its client id.

It's important to note that any clients wishing to use this endpoint must be configured with the urn:openid:params:grant-type:ciba grant type, as this is the grant type used when polling the token endpoint for our access token, but we'll get to that later.

Additionally, because the CIBA flow extends upon OpenID Connect, you must send at least the openid scope in your backchannel authentication request.

We're also required to send precisely one of the following hint parameters.

  • login_hint

This can typically take the form of a username or another user specific value that can be used to identify the end user

  • id_token_hint

A previously issued id_token

  • login_hint_token

A token containing the necessary information used to identify a user

Why do we need hints?

Since CIBA decouples the authentication device and the client application, the OpenID Connect provider does not know which user needs to be authenticated. The client application needs to send a piece of identifying information that the OpenID Connect provider can use to identify the user that is to be authenticated.

Client Preparation

Before getting started, we need to configure a client within our IdentityServer that can authenticate users using CIBA. To make this easier, we will use the AdminUI client creation wizard which, as of AdminUI v6, includes the ability to create clients that use the CIBA grant type.

AdminUI client creation wizard showing a machine created with client initiated bak channel authentication selected.
Next screen, showing the need to enter the CIBA lifetime and polling interval values.

We can see two CIBA specific fields in the updated client wizard. These being:

  • CIBA Lifetime: This is the amount of time a CIBA request can remain valid without user authentication.
  • Polling interval: This is the amount of time a client application has to wait betwen requests to the token endpoint to check if the user has authenticated themselves.

Initiating a CIBA Authentication Request from a Client

Now that our CIBA client is configured, we need to lay the groundwork for the first part of CIBA. That being the CIBA Request to the Backchannel Authentication Endpoint.

To construct this request, we first need to know the backchannel authentication endpoint. We can get this information from our IdentityServer's Discovery Document.

After we have our endpoint, we can construct our BackchannelAuthenticationRequest to send to our Backchannel Authentication Endpoint on our IdentityServer.

Our LoginHint is the username of a user we have in our user store.

We have also included the openid scope.

After this, all that's left to do is call our IdentityServer's Backchannel Authentication Endpoint with our BackchannelAuthenticationRequest object.

After making this request successfully, we get back an AuthenticationRequestId. We need to use this right at the end of the flow when our client is trying to obtain the access token by polling the IdentityServer token endpoint.

However, before our IdentityServer can process this request, we need to implement a couple of things.

IdentityServer Request Preparation

CIBA User Validator

The first bit of implementation-specific code we need to write to process CIBA requests is to implement the IBackchannelAuthenticationUserValidator interface and add it to our Dependency Injection (DI) pipeline

This interface contains the ValidateRequestAsync method that is called during CIBA requests.

The ValidateRequestAsync method returns a BackchannelAuthenticationUserValidatonResult which, if successful, should return the authenticating user's ClaimPrincipal. However, if validation is not successful, we can populate the Error and ErrorDescription fields to return information as to why the request failed to the client.

The ValidateRequestAsync method takes a BackChannelAuthenticationUserValidator object as a parameter. Peering into this object we can see that it contains all the necessary information we need to verify and validate the user the client is requesting authentication for.

Using this information, we can verify that the hint being passed up by the client in the authentication request corresponds to a valid user in our user store.

If our user is null, then we can assume that either the user does not exist in our user store, or the LoginHint passed up by the client is invalid in some way. In either instance, we want to populate the Error and ErrorDescription fields in our request object.

Otherwise, if the user is not null, we can populate the Subject field in our response object with a ClaimsPrincipal constructed using the Claims collection on our user. Remember, we also need to include the user's SubjectId (sub) claim.

 We can now return our BackchannelAuthenticationUserValidatonResult and add our interface implementation to the DI pipeline.

CIBA Notification Service

The next piece of the CIBA request pipeline we need to implement is the delegation of user authentication to the authenticating user's trusted device.

To do this, we need to implement another interface. This time it's the IBackchannelAuthenticationUserNotificationService. This is the service that's used to contact the authenticating user when a CIBA authentication request has been made successfully.

This interface contains the SendLoginRequestAsync method, which takes a BackchannelUserLoginRequest object as a parameter. This object contains all the contextual information needed to send to the user. Most importantly, it contains the InternalId, which is the identifier needed to complete the request in the following section.

For this example, I'm going to be sending an email to the authenticating user using SendGrid. However, the beauty of CIBA is that the delivery method to the user's authentication device is entirely up to you, the implementer. You may choose to send an email, push notification, carrier pigeon, or text message.

Firstly, we need to pull out the relevant information from the BackchannelUserLoginRequest object. In our case, we want to pull out the user's name and email. We'll also pull out the authenticating client's name too, just to flesh out the information presented to the user in the email.

With this information, we can construct an email containing a link to a page in our IdentityServer that will handle the consent for us.

User Consent Screen

The user consent screens are provided to us in the sample. However, I thought I'd take a minute to outline how they work.

Initial Page

In our scenario, the first page a user will see will be the Index page provided by the sample. In this page's backend, we check to see that the InternalId provided to the page in the form of a URL query parameter is a valid CIBA login request.

The validity of the request is tied to the CibaLifetime value that is configured with our client. If we have set the value to 100 (seconds) after the request has been completed, then a user clicking on the email link after these 100 seconds have elapsed will be presented with an error.

A screenshot of an email requesting authentication.

If our InternalId matches a valid login request, then we can allow them to view the page.

Screen showing that the CIBA client is requesting permission.

An important thing to note is that you may also want to display a Binding Message on this page that ensures that the authentication request initiated by the client device is the same that triggers the action on the authentication device. Moreover, you may also want to use a User Code, which is a unique code known only by the Client and Authorization Server that prevents unauthorized authentication requests being sent to the user. These two fields are optional, but if needed can be included in your Authentication Request.

After the user has continued through the Index page, if they don't have an existing session on our IdentityServer, they will be prompted to log in. Afterwards, they'll be shown a typical scope consent page.

A screenshot of a scope consent page.

This page works in the same way as any other consent screen. After the user has consented to our client's access requirements, we can construct a CompleteBackchannelLoginRequest object. We need to make sure we pass the correct InternalId into the class constructor too, otherwise, we won't be able to retrieve the access token from our client. We'll also construct our object with the description the user provided in the consent screen, as well as the scopes they consented to.

After constructing our CompleteBackchannelLoginRequest object, we can call the CompleteLoginRequestAsync method on the IBackchannelAuthenticationInteractionService interface that is injected into the page for us. Calling this method tells our IdentityServer that our CIBA Request is complete.

Getting the Access Token

While all the above is happening, our client needs to be actively waiting and listening for IdentityServer to supply either the access token or a reason to why the access token can't be provided.

The CIBA specification outlines three ways in which a client can be provided with an access token. Poll, Ping, and Push. However, as of the time of writing, IdentityServer only has support for the Poll token retrieval mode.

Polling from the Client

To retrieve our access token when it's ready from our IdentityServer, we need to poll the token endpoint using the CIBA grant type.

Thankfully, this functionality is built into IdentityModel in the form of the RequestBackchannelAuthenticationTokenAsync method. Using this method inside a while loop allows us to poll our IdentityServer regularly until we either receive an error response, an Authorization Pending response, or a Slow Down response.

While inside the loop, we want to send BackchannelAuthenticationTokenRequests to our token endpoint using the AuthenticationRequestId provided to us after our initial CIBA Request.

We can get two error responses back from our IdentityServer while polling the token endpoint. Firstly, the AuthorizationPending token error. This response simply means that IdentityServer is waiting for the user to complete the authentication process. The SlowDown response is returned when our OpenID Connect provider wants us to... slow down... our requests to its token endpoint. The rate at which you poll the token endpoint should be in line with the PollingInterval you have set on your client. However, if you do receive the SlowDown response, you must increase the polling interval by at least 5 seconds.

If all goes well and the user authenticates and grants permission within the lifetime of the authentication request, you should receive an access token from the token endpoint while polling.
 

A screenshot of the token response and access token.

Conclusion

That's it! That's CIBA in action.

In this article we've learned about CIBA, how it's different from traditional OpenID Connect authentication flows, and how we can implement it in IdentityServer using the CIBA User Interaction sample.

If you're looking for a way to easily configure CIBA clients, as well as any other OAuth/OpenID Connect client. Or even if you're looking for a straightforward way to manage your IdentityServer, why not check out AdminUI?