OpenID Connect Hybrid Flow
The OpenID Connect (OIDC) Hybrid Flow combines elements of both the Authorization Code Flow and the Implicit Flow. It allows the client to receive tokens directly from the authorization endpoint (front-channel) while still performing a secure back-channel token exchange. This makes it suitable for server-side applications that need an id_token immediately in the callback — for example, to establish a user session — while deferring the full access_token and refresh_token acquisition to a back-channel call.
This page covers the end-to-end Hybrid Flow as implemented by LoginRadius, including the Authorization Endpoint, front-channel token delivery, and the Token Exchange.
Overview
The flow operates in two distinct phases:
- Authorization Phase — runs in the browser. The client redirects the user to the LoginRadius OIDC Authorization endpoint. After the user authenticates, LoginRadius redirects back to the client's
redirect_uriwith anauthorization_codeand one or more tokens delivered as a URL fragment, depending on theresponse_type. - Token Exchange Phase — runs server-side (back-channel). The client exchanges the
authorization_codefor anaccess_token,id_token, and optionally arefresh_tokenby making a direct POST call to the Token endpoint.
Supported response_type Values
The Hybrid Flow is identified by a response_type that combines code with one or more token identifiers:
response_type | Returned at Authorization Endpoint | Returned at Token Endpoint |
|---|---|---|
code token | code, access_token | access_token, id_token, refresh_token |
code id_token | code, id_token | access_token, id_token, refresh_token |
code token id_token | code, access_token, id_token | access_token, id_token, refresh_token |
When
id_tokenis included inresponse_type, thenonceparameter is required in the authorization request.
Flow Diagram
Step-by-Step Walkthrough
Step 1 — Authorization Request
The client application redirects the user's browser to the LoginRadius OIDC Authorization endpoint with a response_type that combines code with one or more token identifiers.
Endpoint
GET https://{SiteURL}/service/oidc/{OIDCAppName}/authorize
SiteURL= Either<TenantName>.hub.loginradius.comor CustomDomain i.e.auth.your-app.com.OIDCAppName= your configured OIDC App name
Example Request
GET https://your-app.hub.loginradius.com/service/oidc/MyApp/authorize
?client_id=YOUR_OIDC_CLIENT_ID
&response_type=code%20token%20id_token
&scope=openid%20profile%20email
&redirect_uri=https://your-app.com/callback
&state=abc123xyz
&nonce=xyz987abc
nonceis required wheneverid_tokenis included inresponse_type. It is embedded in the returnedid_tokento bind it to the client session and prevent replay attacks.
Step 2 — Session Check
Upon receiving the request, LoginRadius checks whether the user already has an active authenticated session.
- If an active session is found → LoginRadius skips the login UI and immediately redirects to
redirect_uriwith the authorization code and any front-channel tokens. - If no active session → LoginRadius renders the login UI for the user to authenticate.
Step 3 — User Authentication
The user authenticates via the login page. Two login paths are supported:
- Traditional Login — The user submits their credentials (email/password or phone).
- Social Login — The user authenticates via a configured social provider.
After successful authentication, LoginRadius generates a one-time, short-lived
authorization_codealong with the tokens corresponding to the requestedresponse_type, and redirects back to the client'sredirect_uri.
Step 4 — Authorization Response
LoginRadius redirects to the client callback with the authorization_code and any front-channel tokens delivered as a URL fragment (#).
The Hybrid Flow always delivers the authorization response as a URL fragment — even the
code. Parameters arrive as#fragment values, not?query parameters, and are only accessible to client-side JavaScript viawindow.location.hash. They are never sent to the server in the redirect request.
Success Response — response_type=code token id_token
HTTP 302
Location: https://your-app.com/callback
#code={authorization_code}
&access_token={access_token}
&token_type=Bearer
&id_token={id_token}
&expires_in=3600
&state=abc123xyz
Success Response — response_type=code id_token
HTTP 302
Location: https://your-app.com/callback
#code={authorization_code}
&id_token={id_token}
&state=abc123xyz
Success Response — response_type=code token
HTTP 302
Location: https://your-app.com/callback
#code={authorization_code}
&access_token={access_token}
&token_type=Bearer
&expires_in=3600
&state=abc123xyz
Error Response
HTTP 302
Location: https://your-app.com/callback
#error=access_denied
&error_description=...
Errors are returned as fragment parameters on the
redirect_uriby default. This behavior can be changed by passing theresponse_modeparameter in the authorization request.
Step 5 — Token Exchange
The client backend makes a direct POST call to the LoginRadius Token endpoint to exchange the authorization code for tokens. This call never goes through the browser.
Endpoint
POST https://{SiteURL}/api/oidc/{OIDCAppName}/token
The token endpoint accepts the following content type:
| Content-Type | Description |
|---|---|
application/x-www-form-urlencoded | Parameters sent as a URL-encoded form body |
Client Secret Basic
Client Secret Post
The client_id and client_secret are passed as a Base64-encoded Authorization header (Basic Base64(client_id:client_secret)). They must not be included in the request body.
POST /api/oidc/{OIDCAppName}/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic Base64(YOUR_OIDC_CLIENT_ID:YOUR_OIDC_CLIENT_SECRET)
grant_type=authorization_code
&code={authorization_code}
&redirect_uri=https://your-app.com/callback
The client_id and client_secret are included directly in the request body.
POST /api/oidc/{OIDCAppName}/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code={authorization_code}
&client_id=YOUR_OIDC_CLIENT_ID
&client_secret=YOUR_OIDC_CLIENT_SECRET
&redirect_uri=https://your-app.com/callback
Success Response
{
"access_token": "<LoginRadius JWT Access Token>",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "<Refresh Token>",
"id_token": "<JWT ID Token>"
}
Token Response Fields
| Field | Description |
|---|---|
access_token | LoginRadius JWT — use this to call protected APIs |
token_type | Always Bearer |
expires_in | Lifetime of the access token in seconds |
refresh_token | Long-lived token to obtain new access tokens after expiry |
id_token | JWT containing identity claims about the authenticated user |
c_hash and at_hash Claims
When tokens are returned from the authorization endpoint alongside a code or access_token, the id_token includes hash claims to cryptographically bind the tokens together. This prevents substitution attacks where a valid id_token is paired with a malicious code or access_token.
| Claim | Present when | Description |
|---|---|---|
c_hash | code is returned alongside id_token at the authorization endpoint | Left half of the Base64URL-encoded SHA-256 hash of the authorization_code |
at_hash | access_token is returned alongside id_token at the authorization endpoint | Left half of the Base64URL-encoded SHA-256 hash of the access_token |
Clients must validate these hash claims when present:
// Validate c_hash / at_hash — Node.js example
const crypto = require('crypto');
function validateHash(token, hashClaim) {
// Hash the token with SHA-256
const digest = crypto.createHash('sha256').update(token).digest();
// Take the left half and Base64URL-encode it
const leftHalf = digest.subarray(0, digest.length / 2);
const computed = leftHalf.toString('base64url');
// Constant-time comparison to prevent timing attacks
return crypto.timingSafeEqual(
Buffer.from(computed),
Buffer.from(hashClaim)
);
}
// Usage
const isCodeValid = validateHash(authorizationCode, idTokenClaims.c_hash);
const isTokenValid = validateHash(accessToken, idTokenClaims.at_hash);
Error Handling
All errors are returned as fragment parameters appended to the client's redirect_uri:
https://your-app.com/callback#error=ERROR_CODE&error_description=Human+readable+message
Hybrid Flow vs Authorization Code Flow vs Implicit Flow
| Attribute | Authorization Code Flow | Hybrid Flow | Implicit Flow |
|---|---|---|---|
response_type | code | code token, code id_token, code token id_token | token, id_token, token id_token |
| Tokens at authorization endpoint | None | access_token and/or id_token (depending on response_type) | access_token and/or id_token |
| Back-channel token exchange | ✅ Yes | ✅ Yes | ❌ No |
refresh_token supported | ✅ Yes | ✅ Yes | ❌ No |
client_secret required | ✅ Yes | ✅ Yes | ❌ No |
c_hash / at_hash binding | ❌ Not applicable | ✅ Required when applicable | ❌ Not applicable |
| Primary use case | Server-side web apps | Server-side apps needing immediate id_token + back-channel access | Legacy / deprecated |
| PKCE compatible | ✅ Yes | ✅ Yes | ❌ No |
Security Considerations
The Hybrid Flow inherits front-channel token exposure risks from the Implicit Flow for tokens delivered in the authorization response. The back-channel token exchange mitigates the primary risks but the following points apply:
| Risk | Description |
|---|---|
| Token in URL fragment | Front-channel tokens are exposed in the browser URL fragment, making them visible in browser history and accessible to client-side scripts. Validate and discard them promptly; rely on back-channel tokens for long-lived access. |
id_token replay | Always validate the nonce claim in any id_token received at the authorization endpoint. Reject any id_token where the nonce does not match the value sent in the authorization request. |
c_hash / at_hash binding | When id_token is returned alongside code or access_token at the authorization endpoint, the id_token includes c_hash and/or at_hash claims. Validate these to ensure the code and access_token were not substituted in transit. |
state validation | Always validate the state parameter returned in the callback matches the value sent in the authorization request to prevent CSRF. |
redirect_uri exact match | The redirect_uri in the token request must exactly match the one used in the authorization request. Mismatches are rejected by LoginRadius. |
For most new server-side applications, the Authorization Code Flow is preferred over the Hybrid Flow unless there is a specific requirement to receive an
id_tokenimmediately in the front channel.