How to implement client-based sessions

This is a special guide for Connect2id server integrators who need to deal with the requirement to support browser sessions tied to individual clients.

The traditional session model

How does this differ from the traditional session model? Normally, once a user is successfully authenticated with the Identity Provider (IdP) a session ID in the form of a cookie is stored in the browser. Subsequent OpenID Connect requests from any client via the same browser will not require the user to re-authenticate, unless this is specifically requested or the session / cookie has expired. This model is implemented by all popular public IdPs such as Google.

Side note on requesting re-authentication

Clients can explicitly request (re)authentication with OpenID Connect by setting the prompt=login request parameter.

Re-authentication is also triggered if the client requests a higher authentication strength than the previously used for the session. This is done with the optional acr_values (Authentication Context Class Reference) parameter. Note for the ACR option to work the IdP must support the appropriate authentication mechanisms (e.g. hardware token, email verification) to be supported of course.

Tying sessions to individual clients

User sessions can also be tied to each individual client. This special model essentially turns the IdP into a private session manager for the client application and has the following effects:

  • Single Sign-On (SSO) is disabled.
  • A separate and fully independent IdP session is created for each user login to a given client via the same browser.
  • Users will be required to authenticate for every new login with a given client.
  • Users may log in with different identities (user ID) to the IdP in the same browser.
  • Each client session can have unique properties (lifetime and max idle time settings, data).
  • When a session expires or the user chooses to log out, this will only affect the session with the corresponding client. Other sessions of the user in the same browser will not be affected.

Hybrid / two-tier session model

To keep the Single Sign-On (SSO) capability one could devise a hybrid session model:

  • One main regular (browser-only-tied) IdP session is created solely for the purpose of authenticating the user.
  • When an OpenID Connect request is received the user identity is taken from the main session, then an individual client session is created as described above.

One could also implement a two-tier model where certain OpenID Connect clients are given individual sessions, while the rest use the traditional session model.

How to implement client-based sessions with the Connect2id server

The Connect2id server was originally designed with the traditional session model in mind, but can also support a session-per-client model.

Cookie encoding

For that the cookie format must be changed to include the OAuth 2.0 / OpenID Connect client_id in addition to the session ID generated by the Connect2id server.

Cookie format for traditional session model

The cookie value contains the session ID provided by the Connect2id server:

sid=[session_id]

Cookie format for client-based session model

The name contains the client_id to enable the login page to set multiple session cookies (one per client) while preventing collision between them; the value contains the [ client_id, session_id ] tuple:

sid-[client-id]=HMAC[client-id,session-id]

The cookie value must be cryptographically protected so that the client_id cannot be tampered by a malicious user. A simple but effective mean to secure the cookie value is by applying an HMAC to it, generated with a secret key that is only known to the login page (IdP).

The JSON Web Token (JWT) standard offers a simple way to encode the cookie payload with HMAC protection in a compact and URL-safe string.

The open-source JWT library can be used to create the HMAC-protected cookie payload.

The HMAC secret must be generated using a cryptographically secure method and be at least 256 bits long. To generate such a secret in Java:

import java.security.SecureRandom;

// Generate random 256-bit (32-byte) secret
byte[] secret = new byte[32];
new SecureRandom().nextBytes(secret);

The secret must be stored in safe place with the login page, and is required to create and verify all client-based session cookies.

How to encode the cookie as an HMAC-protected JWT:

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jwt.*;

// Create HMAC signer (it is thread-safe, so you can keep it around)
JWSSigner signer = new MACSigner(secret);

// The OAuth 2.0 / OpenID Connect client_id
String cliendID = "zuMa2ohj";

// The session ID
String sid = "Iagood0hToo3AhviAepahv6uChecaev7";

// Prepare JWT with payload (claims set)
JWTClaimsSet claimsSet = new JWTClaimsSet();
claimsSet.setClaim("client_id", clientID);
claimsSet.setClaim("sid", sid);

// Create JWT and specify required protection (HMAC with SHA-256)
SignedJWT jwt = new SignedJWT(new JWSHeader(JWSAlgorithm.HS256), claimsSet);

// Apply the HMAC protection
jwt.sign(signer);

// Serialize the JWT compact form, produces something like
// eyJhbGciOiJIUzI1NiJ9.SGVsbG8sIHdvcmxkIQ.onO9Ihudz3WkiauDO2Uhyuz0Y18UASXlSc1eS0NkWyA
String cookieValue = jwt.serialize();

The cookie can then be set like this using the HTTP Set-Cookie header:

  • The cookie name is formed of a suitable prefix (e.g. sid), a delimiter (e.g. -) and the client_id value.

  • The cookie value is the JWT.

Example:

Set-Cookie: sid-zuMa2ohj=eyJhbGciOiJIUzI1NiJ9.SGVsbG8sIHdvcmxkIQ.onO9Ihudz3WkiauDO2Uhyuz0Y18UASXlSc1eS0NkWyA

To decode and verify the cookie the HMAC must be checked. Also, it must be ensured that the unprotected client_id in the cookie name matches the protected client_id value in the JWT.

// Parse the JWT
SignedJWT jwt = SignedJWT.parse(cookieValue);

// Create HMAC verifier (thread-safe, so you can keep it around)
JWSVerifier verifier = new MACVerifier(secret);

// Check HMAC; if that fails the cookie has been tampered with!
assertTrue(jwt.verify(verifier));

// Retrieve the session ID
String sessionID = jwt.getJWTClaimsSet().getStringClaim("session_id"));

// Retrieve the OAuth 2.0 / OpenID Connect client_id
String clientID = jwt.getJWTClaimsSet().getStringClaim("client_id");

// Check that the client_id from the cookie name matches the protected value;
// if that fails the cookie has been tampered with!
assertTrue(clientIDFromCookieName.equals(clientID));

Retrieving and setting the session cookie

When servicing a new OpenID Connect request at the login page the interaction with the Connect2id server authorisation session endpoint remains the same, save for having a slightly different procedure to get and determine the client-tied session ID:

  1. Extract the client_id from the OpenID Connect authentication request (encoded in the URL query). You can do that with help of the Nimbus OpenID Connect SDK or some other method.

  2. Check if a cookie with the name sid-[client_id] is present. If not continue as normal.

  3. If a cookie is found, decode it and verify it as shown above.

  4. Pass the session ID to the Connect2id authorisation session endpoint (as normal).

When the Connect2id server creates a new session for the login page, encode the cookie it was suggested above. The Connect2id server does not need to be aware that the session is special and tied to an individual client, only the login page which sets and reads the cookie has to.

You can also use the optional data attribute of the session object to record for which client it was created. So when sessions are queried / monitored it can be easily determined which client they are associated with.

Questions?

If you need help with implementing client-tied session, don't hesitate to contact our support.