A practical look at identity, sessions, OAuth 2.0, OpenID Connect, and tenant isolation.
Single Sign-On is often summarized as "log in once and access many applications." That is correct, but incomplete. A real SSO system must also answer several security questions:
- Which application is requesting access?
- Which organization owns that application?
- Is the user allowed to use it?
- What information did the user approve sharing?
- How can the application trust the tokens it receives?
The Three Actors
An SSO flow has three main actors:
- The end user who wants to sign in.
- The client application that needs the user's identity.
- The identity provider, which authenticates the user and issues tokens.
The client application never receives the user's password. Instead, it redirects the browser to the identity provider. The provider handles authentication and sends the application a short-lived authorization code.
This separation is fundamental: applications delegate authentication to one trusted identity provider.
How the Sign-In Flow Works
A secure implementation can use the OAuth 2.0 Authorization Code Flow with OpenID Connect and PKCE.
The sequence is:
- A client redirects the browser to the identity provider with its
client_id, callback URL, requested scopes,state, and an S256 PKCE challenge. - The provider validates the client and callback URL before showing any login page.
- It resolves the tenant from the registered client, so tenant context does not come from untrusted user input.
- The provider stores the validated request as a short-lived authorization transaction. The frontend receives only an opaque transaction ID.
- The user signs in. The provider verifies the tenant-scoped account, password, account status, and role for that client.
- The user approves or denies the requested access on a consent screen.
- On approval, the provider returns a short-lived, single-use authorization code to the registered callback.
- The client exchanges the code and its PKCE verifier for an ID token, access token, and refresh token.
The ID token tells the client who authenticated. The access token authorizes API access. The refresh token allows the client to obtain a new short-lived access token without asking the user to sign in again.
SSO Flow Diagram
Where "Single" Sign-On Happens
The reusable part is the identity-provider session.
After login, the provider creates an opaque server-side browser session and stores its identifier in a host-only, HttpOnly, SameSite=Lax cookie. A later authorization request can reuse that session, including for another registered application in the same tenant.
However, reusing authentication does not mean automatically granting access.
The provider still checks that:
- the session belongs to the client's tenant;
- the user is still active;
- the user still has a role for the requesting client; and
- the current request satisfies rules such as
prompt=loginormax_age.
This reveals an important SSO principle:
Authentication can be shared, but authorization must be evaluated for every application.
A user may be signed in to the identity provider and still be denied access to a specific client.
Multi-Tenancy Changes the Trust Boundary
In a multi-tenant platform, the same SSO service supports many organizations. Isolation is therefore part of authentication itself, not merely a database concern.
A multi-tenant implementation should associate users, OAuth clients, roles, sessions, and tokens with a tenant. The client_id can determine the tenant during authorization, while issued tokens can contain both tenant_id and client_id claims.
This prevents a valid identity from Tenant A from silently becoming valid for an application owned by Tenant B.
Why PKCE, State, and Redirect URIs Matter
Several small-looking values protect the flow:
- PKCE binds the authorization request to the client that started it. An intercepted code is useless without the original verifier.
- State lets the client detect forged or mismatched callbacks.
- Registered redirect URI matching prevents the provider from sending codes to an attacker-controlled destination.
- Single-use authorization codes reduce replay risk.
- Nonce and authentication time preserve OpenID Connect context in the ID token.
S256 PKCE should protect both public clients, such as SPAs and mobile apps, and confidential server-side clients. Confidential clients can also authenticate with a secret that is stored as a hash and shown only when created or rotated.
Tokens Are Verifiable, Not Magical
The identity provider can sign ID and access tokens with RS256. Applications retrieve public keys from a JWKS endpoint and verify token signatures without receiving the private signing key.
Verification must include more than the signature. A client should also validate the issuer, audience, expiration, tenant, and expected nonce. Roles and scopes can then support application-level authorization.
Access tokens are deliberately short-lived. Refresh tokens live longer, are stored hashed, rotate when used, and can be revoked. This limits damage while preserving a smooth user experience.
Architecture Supports the Security Model
A practical implementation can follow Clean Architecture:
- domain entities describe clients, users, roles, codes, tokens, and sessions;
- use cases implement authorization, login, consent, token exchange, refresh, and logout;
- PostgreSQL stores durable identity and configuration data;
- Redis stores short-lived transactions, sessions, codes, and throttling counters;
- HTTP handlers translate protocol requests into use-case calls.
This separation keeps OAuth rules independent from specific web frameworks and storage technologies, making security behavior easier to test.
The Main Lesson
SSO is not simply a shared login form. It is a trust system connecting identities, organizations, applications, browser sessions, consent, and cryptographically verifiable tokens.
authenticate once, validate access per client, keep tenant boundaries explicit, and trust only server-validated protocol state.
That is what turns "log in once" from a convenience feature into a secure identity architecture.
I will share the source code related to this SSO implementation through the link below:
Source code: Keyles
Top comments (0)