Skip to content
Connect2id

How to integrate a login UI and authentication factors with a Connect2id server

This guide shows integrators and frontend developers how to integrate a login UI and user authentication methods with a Connect2id server. Examples include passkeys, FIDO2 hardware security keys, time-based one-time passwords (TOTP) from authenticator apps, and traditional password checks.

The Connect2id server provides a web API for binding a login UI, user authentication and consent policies

The login flow and user interaction

Consider Alice, who wants to log into Wonderland App using OpenID Connect. Alternatively, Wonderland App may make an OAuth 2.0 authorisation request to access a protected resource owned by Alice.

The web or mobile application sends Alice’s browser to her OpenID provider / authorisation server. There, Alice may be asked to authenticate and then give her consent. For OpenID Connect, this means agreeing to log into Wonderland App and, if requested, share profile information. If Alice grants the request, the server sends her browser back to Wonderland App with an authorisation response.

The server must establish the user’s identity and authorisation for the request. An existing session or previously recorded consent may satisfy these requirements, allowing the corresponding screen to be skipped:

  • After Alice authenticates, a session cookie can be stored in her browser. When another authorisation request arrives, the Connect2id server can use this cookie to identify her existing session. If the session is valid and meets the new request’s authentication requirements, Alice does not need to authenticate again.

  • Alice’s consent to log into Wonderland App and grant any requested access can be stored until revoked. Subsequent requests from the same app can then skip the consent screen if the stored consent covers the request. An implicit consent policy may also apply to selected trusted apps.

The authorisation session web API

Alice interacts with her OpenID provider / authorisation server at the authorisation endpoint, where she allows or denies the client’s request. For OpenID Connect, the request concerns her identity and profile information.

The authentication and consent UIs are decoupled from the Connect2id server through the authorisation session web API. This gives integrators the flexibility to implement their own user experience, authentication methods and consent policies.

The login handler manages all user interaction and verifies Alice’s credentials outside the Connect2id server, for example by querying a password database or interacting with a FIDO device.

This API approach offers several benefits:

  • Authentication methods and policies, including federated login through 3rd party IdPs, can be integrated independently of the Connect2id server.

  • Frontend developers can use their preferred language and framework, reuse existing code and assets, and create branded or personalised experiences. Separate login UIs can serve different audiences, such as employees and consumers, or different devices.

  • The frontend can be hosted independently of the Connect2id server, with a high-availability proxy and CDN if needed. Frontend updates can be deployed without downtime.

  • User credentials remain with the authentication infrastructure and are not disclosed to the Connect2id server.

Interaction with the authorisation session API resembles the MVC pattern. The login handler passes the client’s OpenID Connect or OAuth 2.0 request to the Connect2id server for processing. When prompted by the server, the handler performs the following tasks:

  • Authenticate or reauthenticate the user. The server typically requests this when there is no valid user session or the client requires a higher authentication level (ACR). The handler might display a credentials form and check the credentials against an LDAP directory. On success, it submits the user’s ID to the Connect2id server.

  • Obtain the user’s consent. This covers the requested OAuth 2.0 scope values and OpenID Connect claims and is needed when the server has no stored consent covering the request for this user and client. The handler can obtain explicit consent through a form or apply an implicit consent policy.

Once Alice’s identity and consent are established, the Connect2id server generates the authorisation response for the handler to relay to the client.

Configuring the login page URL

Configure the login page URL as the Connect2id server’s OAuth 2.0 authorisation endpoint.

Example:

op.authz.endpoint=https://myidp.com/openid

The Connect2id server advertises this URL in its OpenID provider / authorisation server metadata. Clients and developers can use these standard JSON documents to discover the server’s endpoints and capabilities:

https://[base-server-url]/.well-known/openid-configuration
https://[base-server-url]/.well-known/oauth-authorization-server

General guidelines

Follow these guidelines when implementing the login and consent handler:

  • The login handler can be implemented in any programming language and framework that supports server-side HTTP requests and secure browser session handling. Choose a stack based on your team’s expertise, deployment environment and available libraries for the authentication methods you intend to support.

  • The handler must have access to the authorisation session web API and use the configured API token in the Authorization header of every request. Store the token securely.

  • Make all requests to the authorisation session web API through the server-side of the login handler.

  • Store context needed during an authorisation session, such as the current step in multi-factor authentication, in the session’s optional data object.

  • Store information needed across OpenID Connect or OAuth 2.0 requests in the data field of the IdP session that the Connect2id server creates for the logged-in user.

How to use the authorisation session web API

The following diagram shows how the authorisation session API guides the login handler through authentication and consent, where needed, and returns the final authorisation response for the client.

Step 1. Submit the request and session cookie to the Connect2id server

When the login handler receives an OpenID Connect authentication request or an OAuth 2.0 authorisation request, it must submit the request to the Connect2id server, together with the session cookie if present.

Extract the URL query string and the session cookie, if present, from the incoming HTTP request. Use these values to start a new authorisation session.

Example OpenID authentication request:

https://c2id.com/login?
 response_type=code
 &client_id=123
 &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
 &scope=openid+email
 &state=KEbMte3qrtNau8C7PsU1VLxd674BQfjKCARDFR1JWnE
 &display=popup

The extracted query string:

response_type=code
 &client_id=123
 &redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb
 &scope=openid+email
 &state=KEbMte3qrtNau8C7PsU1VLxd674BQfjKCARDFR1JWnE
 &display=popup

Submitting the query string, together with the session cookie (if any) to the Connect2id server:

POST /authz-sessions/rest/v3/ HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "query"   : "response_type=code&client_id=123&redirect_uri=https%3A%2F%2Fclient.example.com%2Fcb&scope=openid+email&state=KEbMte3qrtNau8C7PsU1VLxd674BQfjKCARDFR1JWnE&display=popup",
  "sub_sid" : "WYqFXK7Q4HFnJv0hiT3Fgw.-oVkvSXgalUuMQDfEsh1lw"
}

The Connect2id server will pre-process the OpenID Connect request and proceed to the next step.

Step 2. Handle the Connect2id server response

The Connect2id server returns a JSON object whose type member identifies the next action for the login handler:

  • auth: authenticate the user in step 3.

  • consent: obtain consent in step 4.

  • response: relay the final response in step 5.

  • error: display the error in step 6.

Step 3. Authenticate the user

The Connect2id server prompts the login handler to authenticate the user with a JSON message such as:

{
  "type"           : "auth",
  "sid"            : "g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE",
  "display"        : "popup",
  "select_account" : false
}

The sid identifies the authorisation session. Use it in subsequent requests to the API. It is distinct from the subject session ID.

The display parameter hints at how to render the UI. See the possible values in the OpenID Connect Core specification. Make the UI responsive to support different screen sizes.

The optional select_account parameter is usually omitted. When true, it indicates that the client requests an account selection interaction.

The JSON message that prompts the login page to authenticate the user is described in the authorisation session API reference.

The authentication method depends on the IdP’s policy and infrastructure. For example, the handler can display a credentials form and check the supplied credentials against an LDAP or Active Directory server. It can also require an additional factor, such as a hardware token or a code sent to the user’s smartphone.

The handler can also authenticate the user through a trusted third-party identity provider, such as Google, Apple or a workplace IdP. In this case, it completes the upstream login, validates the response and maps the upstream identity to a local user ID. See the guide how to implement identity federation for details.

If the Connect2id server is configured with multiple authentication levels (ACRs), clients can request a specific level for all logins or for sensitive operations, such as authorising a financial transaction. The required level is indicated by the acr parameter in the authentication prompt.

After successful local or federated authentication, the login handler must submit the user’s local ID to the Connect2id server, using the subject authentication JSON object:

PUT /authz-sessions/rest/v3/g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "sub" : "alice"
}

Send this HTTP PUT request to the authorisation session identified by the sid received above.

If the IdP supports multiple authentication levels (ACRs), include the level at which the user authenticated:

PUT /authz-sessions/rest/v3/g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "sub" : "alice",
  "acr" : "http://loa.c2id.com/high"
}

The handler can also supply settings or override defaults for the user session that the Connect2id server creates for Alice.

For example, set the session’s maximum idle time to one week (60 × 24 × 7 = 10080 minutes) and store Alice’s name and email address in the session so the login UI can greet her on subsequent visits:

PUT /authz-sessions/rest/v3/g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "sub"      : "alice",
  "acr"      : "http://loa.c2id.com/high",
  "max_idle" : 10080,
  "data"     : { "name"  : "Alice Adams",
                 "email" : "[email protected]" }
}

After submitting the authentication and session details, return to step 2 to handle the server’s response.

Step 4. Obtain the user’s consent

When the server returns a consent prompt, the login handler must obtain authorisation for the login and any requested access represented by OAuth 2.0 scope values or OpenID Connect claims.

Here is an example consent prompt from the Connect2id server:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "type"        : "consent",
  "sid"         : "g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE",
  "display"     : "popup",
  "sub_session" : { "sid"           : "WYqFXK7Q4HFnJv0hiT3Fgw.-oVkvSXgalUuMQDfEsh1lw",
                    "sub"           : "alice",
                    "auth_time"     : 12345678,
                    "creation_time" : 12345678,
                    "acr"           : "http://loa.c2id.com/high",
                    "max_life"      : 302400,
                    "auth_life"     : 20160,
                    "max_idle"      : 10080,
                    "data"          : { "name"  : "Alice Adams",
                                        "email" : "[email protected]" }},
  "client"      : { "client_id"        : "123",
                    "client_type"      : "confidential",
                    "application_type" : "web",
                    "name"             : "Wonderland App",
                    "uri"              : "http://wonderland.com"},
  "scope"       : { "new"       : [ "openid", "email" ],
                    "consented" : [ ] },
  "claims"      : { "new"       : { "essential" : [ "email", "email_verified" ],
                                    "voluntary" : [ ] },
                    "consented" : { "essential" : [ ],
                                    "voluntary" : [ ] } }
}

The sid and display parameters have the same meanings as in step 3.

The Connect2id server also includes the details of the session that Alice has with the IdP after her authentication (sub_session for subject session).

Use sub_session.sid to create or update the browser’s session cookie, which the handler submits on subsequent visits (see step 1). The cookie’s expiration can be based on sub_session.max_idle. Alternatively, use a persistent cookie and let the Connect2id server enforce session expiration. Use sub_session.data to personalise the UI.

The client object contains the client’s registered details, which the frontend can use to display its name, information and icon. These details can also inform consent policies, such as restricting which clients may receive tokens with privileged scope values.

The scope and claims parameters list the requested OAuth 2.0 scope values and OpenID Connect claims. Each distinguishes newly requested values from those for which Alice’s consent is already on record. Claims are further divided into essential and voluntary claims according to OpenID Connect semantics.

Handle the consent prompt according to the IdP’s policy.

Consent may be explicit, with a form for Alice to record her choice, implicit under the IdP’s policy, or a combination of both.

In each case, submit the authorised scope values and claim names to the Connect2id server.

Send a PUT request to the authorisation session with the authorised scope and claims:

PUT /authz-sessions/rest/v3/g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "scope"  : [ "openid", "email" ],
  "claims" : [ "email", "email_verified" ]
}

Note that the authorised scope and claims can exclude some of the originally requested ones (indicating they are denied), or include additional non-requested ones.

The submitted consent supports a number of options, such as overriding individual parameters of the tokens to be issued, or implementing act-as / on-behalf-of use cases.

If Alice declines to log into the client or denies all requested access, DELETE the authorisation session. The Connect2id server then generates an access_denied response for the handler to relay to the client.

DELETE /authz-sessions/rest/v3/g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6

After granting or denying consent, return to step 2 to handle the server’s response.

Step 5. Relay the final response back to the client

At this step, the Connect2id server has generated the final authorisation response for the handler to relay to the client.

If the response includes sub_sid, use it to create or update the browser’s session cookie. This field is included when the user is authenticated or reauthenticated and the consent step is skipped because stored consent covers the requested scope values and claims.

How the response is relayed to the client depends on the mode parameter.

For the redirection-based modes (query, query.jwt, fragment and fragment.jwt), redirect the browser to parameters.uri, which already contains the encoded authorisation response.

Example final response for query:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "type"       : "response",
  "mode"       : "query",
  "parameters" : { "uri" : "https://client.example.com/cb?code=5rZ_KxqZ&state=Nygv4CLQ" }
}

Use HTTP 303 See Other for these redirects, with parameters.uri in the Location header. This ensures that, following a POST such as a consent form submission, the browser issues a GET without forwarding the POST body to the client.

HTTP/1.1 303 See Other
Location: https://client.example.com/cb?code=5rZ_KxqZ&state=Nygv4CLQ

For the form POST modes (form_post and form_post.jwt), return an HTML page containing a form that submits to parameters.uri using HTTP POST. Render the key / value pairs from parameters.form as hidden form inputs, with all names and values properly HTML-escaped, and auto-submit the form to the client. See response via form POST for an example.

For custom response modes, encode and relay the response according to the mode’s contract. See custom response mode parameters.

Step 6. Display the error

If a client sends an invalid request, the Connect2id server may return a non-redirecting error.

Unlike authorisation errors such as access_denied, which are relayed to the client, a non-redirecting error must be handled locally. Do not redirect the user’s browser to the client.

Display the error in the login UI so the issue can be reported to the client’s developers.

HTTP/1.1 200 OK
Content-Type: application/json

{
  "error"             : "invalid_request",
  "error_description" : "Missing \"redirect_uri\" parameter"
}

Sample login page

The Connect2id server includes an example login page implemented in Java, without JavaScript, to reduce the client-side attack surface:

https://github.com/Connect2id/c2id-server-login-logout

Tips

1. How to store additional state in a login session?

Store context needed while obtaining credentials or consent in the authorisation session’s optional data JSON object.

Examples:

  • The current step in multi-factor authentication. For a flow involving a CAPTCHA, a username and password check, and an OTP, store the current step and information needed for later steps, such as the resolved user ID.

  • Consent that spans multiple screens.

  • Active Directory / LDAP attributes, such as group membership, obtained during the user password check for use as policy inputs during consent.

Set and read the data object through its dedicated resource. Once set, it is included in subsequent authentication and consent prompts.

Important: Access the data resource only through back-channel calls from the login handler’s server. Do not expose it to the browser.

2. How to always trigger the authentication prompt?

To run an operation at every authentication step, even when the user has a valid session and cookie, set op.authz.alwaysPromptForAuth:

op.authz.alwaysPromptForAuth=true

To receive the consent prompt even when stored consent covers all requested scope values and claims, set op.authz.alwaysPromptForConsent:

op.authz.alwaysPromptForConsent=true

4. How to handle multiple user realms?

SaaS providers and organisations with multiple user or security realms may need tokens to identify the user’s realm to clients and resource servers (web APIs).

A realm can be represented by a DNS domain name, an Active Directory Domain Services (AD DS) domain, or another identifier.

First, define a subject (user ID) scheme that includes the realm, such as alice@wonderland or the AD DS-style Wonderland\alice.

During authentication, obtain the realm by parsing the domain of the user’s email address or displaying an account or realm chooser. The backend can then check the credentials against the appropriate user store or upstream IdP.

After successful authentication, submit the subject using the chosen scheme:

PUT /authz-sessions/rest/v3/g6f5K6Kf6EY11zC00errCf64yLtg9lLANAcnXQk2xUE HTTP/1.1
Host: c2id.com
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
Content-Type: application/json

{
  "sub" : "alice@wonderland"
}

To apply realm-specific policies during consent, extract the realm from sub. Pass additional policy inputs through the data resource.

In all tokens issued to the client for Alice the subject (sub) will then appear as alice@wonderland.

In ID tokens:

{
  "sub" : "alice@wonderland",
  "iss" : "https://openid.c2id.com",
  "aud" : "client-123",
  ...
}

In access tokens, JWT-encoded or introspected:

{
  "sub"       : "alice@wonderland",
  "iss"       : "https://openid.c2id.com",
  "client_id" : "client-123",
  ...
}

A token codec plugin can further customise tokens to meet a realm’s requirements.

Note: For complete separation of security realms, including users, sessions, clients and resource servers, consider the multi-tenant edition of the Connect2id server.