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
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.
Disabling SSO for selected clients
If complete separation of the client-based sessions is not required, consider simply disabling SSO for selected clients. This is a configuration that requires a JSON query to determine which clients must not use SSO.
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.
For that the cookie format must be changed to include the OAuth 2.0 / OpenID
client_id in addition to the session ID generated by the Connect2id
Cookie format for traditional session model
The cookie value contains the session ID provided by the Connect2id server:
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 [
session_id ] tuple:
The cookie value must be cryptographically protected so that the
cannot be tampered by a malicious user. A simple but effective mean to secure
the cookie value is by applying an
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.
Our 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; 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
The cookie value is the JWT.
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
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:
Check if a cookie with the name
sid-[client_id]is present. If not continue as normal.
If a cookie is found, decode it and verify it as shown above.
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 as 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.
Note that the
rps attribute of the session
object can be used to find out
which client was logged in during the session.