In the previous chapter, we saw how OneIdAuthenticationExtensions like AddOneId() act like the power switch, plugging our OneID configuration into our web application. Now that the power is on, who actually does the work? Who manages the security checks when a user tries to log in using OneID?
Meet the OneIdAuthenticationHandler – the security guard and engine of the OneID middleware.
Imagine you've hired a security company (the oneid-oauth-middleware) for your office building (your web application). You've given them instructions on who to let in and where to send them (using OneIdAuthenticationOptions), and you've set up their post at the entrance (using OneIdAuthenticationExtensions).
But you still need an actual guard at the post to perform the checks! This guard needs to:
- Guide Visitors: When someone wants to enter, guide them to the official ID checkpoint (the OneID login page).
- Handle Return: When they come back with an entry pass (an authorization code), check it.
- Verify Securely: Go behind the scenes (the backchannel) to securely exchange that pass for official badges (access token, ID token).
- Validate Badges: Make sure the badges are legitimate and haven't expired.
- Grant Access: Create an official visitor pass for your building (the user's identity or
ClaimsPrincipal) so they can access authorized areas.
The OneIdAuthenticationHandler is this diligent security guard. It's the component within the middleware that actively manages every step of the OneID login process after you've enabled it. Without it, the middleware wouldn't know how to actually talk to OneID or handle the user's login attempt.
It's important to know that this handler comes in two slightly different versions, tailored for the specific web framework you are using:
- One version for modern ASP.NET Core applications.
- Another version for older ASP.NET Framework applications using OWIN/Katana.
While their internal details differ slightly to match their frameworks, their core job remains the same: managing the OneID authentication flow. You generally don't interact with the handler directly; the framework uses it automatically based on the setup you did in Chapter 3. However, understanding its role helps you grasp how the entire login process works.
Let's follow our security guard, the OneIdAuthenticationHandler, as it manages a user's login attempt:
-
Challenge Issued:
- A user clicks "Login with OneID" in your application.
- Your application tells the authentication system, "Challenge the user with the OneID scheme."
- The
OneIdAuthenticationHandlerwakes up.
-
Building the Detour:
- The handler reads the OneIdAuthenticationOptions you configured (like
ClientId,Environment,Scope,CallbackPath). - It constructs a special URL pointing to the correct OneID login page (based on the
Environment). This URL includes information like yourClientId, the permissions (Scope) you're asking for, and theCallbackPathwhere OneID should send the user back. - (Handler Method:
BuildChallengeUrl)
- The handler reads the OneIdAuthenticationOptions you configured (like
-
Redirecting the User:
- The handler tells the user's browser to go to that OneID URL.
- The user leaves your site temporarily and sees the familiar OneID login screen.
-
User Authenticates at OneID:
- The user enters their OneID username and password (and possibly performs multi-factor authentication) directly on the secure OneID site. Your application never sees these credentials.
-
Returning with a Code:
- If login is successful, OneID redirects the user's browser back to the
CallbackPathyou specified in your options. - Crucially, OneID adds a temporary, single-use
code(an authorization code) to this redirect URL.
- If login is successful, OneID redirects the user's browser back to the
-
Handling the Callback:
- The user's browser arrives back at your application's
CallbackPath. - The
OneIdAuthenticationHandlerintercepts this incoming request because it's registered to handle this specific path. - It extracts the
codeand also verifies astateparameter to prevent cross-site request forgery (CSRF) attacks. - (Handler Method:
HandleRemoteAuthenticateAsyncor equivalent)
- The user's browser arrives back at your application's
-
Exchanging the Code for Tokens (Securely):
- This is a critical step! The
codeitself doesn't contain user information. - The handler now makes a direct, secure, server-to-server connection to OneID's
TokenEndpoint(the URL for this is determined by theEnvironmentin your options). This is called the "backchannel". - It sends the
code, your application'sClientId, and uses your application's certificate to prove its identity (this certificate usage is often handled by the OneIdAuthenticationBackChannelHandler, which the main handler uses). - (Handler Method:
ExchangeCodeAsync)
- This is a critical step! The
-
Receiving the Tokens:
- If the code is valid and the certificate is trusted, OneID securely sends back a set of tokens:
- ID Token: Contains information about the authenticated user (like their unique ID, name, etc.). It's like their verified identity card.
- Access Token: Allows your application to call specific OneID-protected APIs or eHealth Ontario services (like OLIS or DHDR) on behalf of the user, if you requested the necessary scopes. It's like a temporary key card for specific service rooms.
- Refresh Token (Optional): Allows your application to get a new access token after the current one expires, without requiring the user to log in again. It's like a way to renew the key card automatically for a while.
- If the code is valid and the certificate is trusted, OneID securely sends back a set of tokens:
-
Validating and Processing Tokens:
- The handler receives these tokens.
- It validates the
ID Token's signature (using information from OneID) and checks claims like expiration time (exp) and audience (aud) to ensure it's legitimate and meant for your application. - It extracts the user's information (claims) from the validated
ID Token. - (Handler Method:
CreateTicketAsyncand helpers likeExtractClaimsFromToken)
-
Creating the Application Identity:
- The handler takes the verified user claims and builds a
ClaimsPrincipalobject. This object represents the logged-in user within your application's security context (like ASP.NET Core Identity).
- The handler takes the verified user claims and builds a
-
Saving Tokens (Optional):
- If you configured
SaveTokens = truein OneIdAuthenticationOptions, the handler saves the received tokens (ID, access, refresh, based onTokenSaveOptions) so your application can retrieve and use them later (e.g., using the access token to call an eHealth service). - (Handler Method:
CreateTicketAsync)
- If you configured
-
Signing In:
- Finally, the handler tells the web framework (ASP.NET Core or OWIN) to sign the user in, usually by creating an authentication cookie containing the
ClaimsPrincipal. - The user is now officially logged into your application using their OneID credentials!
- Finally, the handler tells the web framework (ASP.NET Core or OWIN) to sign the user in, usually by creating an authentication cookie containing the
Phew! That's a lot of steps, but the OneIdAuthenticationHandler manages this entire complex dance for you, ensuring it's done securely according to the OAuth 2.0 and OpenID Connect standards.
Let's visualize the main flow managed by the handler:
sequenceDiagram
participant User
participant Browser
participant YourApp as Your Application (Handler)
participant OneIDPlatform as OneID Login Platform
User->>Browser: Clicks "Login with OneID"
Browser->>YourApp: Request protected resource / Trigger login
YourApp->>YourApp: Handler reads Options, builds Auth URL (BuildChallengeUrl)
YourApp-->>Browser: Redirect to OneID Auth URL
Browser->>OneIDPlatform: Request OneID Auth URL
OneIDPlatform-->>Browser: Show Login Page
User->>OneIDPlatform: Enters Credentials
OneIDPlatform->>OneIDPlatform: Authenticates User
OneIDPlatform-->>Browser: Redirect back to YourApp (CallbackPath) with `code` & `state`
Browser->>YourApp: Access CallbackPath with `code` & `state`
YourApp->>YourApp: Handler verifies `state`, extracts `code` (HandleRemoteAuthenticateAsync)
YourApp-)OneIDPlatform: Exchange `code` for tokens using backchannel (ExchangeCodeAsync)
Note over YourApp, OneIDPlatform: Uses ClientId & Certificate
OneIDPlatform--)YourApp: Return ID Token, Access Token, Refresh Token
YourApp->>YourApp: Handler validates tokens, extracts claims (CreateTicketAsync)
YourApp->>YourApp: Creates ClaimsPrincipal, Saves Tokens (if configured)
YourApp->>YourApp: Signs user into application session (e.g., creates cookie)
YourApp-->>Browser: Show logged-in content
Code Insight:
You don't usually call these methods directly, but seeing their structure helps understand the process. The code lives in OneIdAuthenticationHandler.NetCore.cs (for ASP.NET Core) and OneIdAuthenticationHandler.NetFull.cs (for OWIN/Katana).
-
Building the Challenge URL (ASP.NET Core Example): This method adds OneID-specific parameters like
aud(audience) and_profile(service profiles) to the standard OAuth challenge URL.// From: src/AspNet.Security.OAuth.OneID/OneIdAuthenticationHandler.NetCore.cs protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri) { // Calls the base class to build the standard OAuth URL var challengeUrl = base.BuildChallengeUrl(properties, redirectUri); // Adds the 'aud' parameter required by OneID challengeUrl = QueryHelpers.AddQueryString(challengeUrl, "aud", ClaimNames.ApiAudience); // Adds the '_profile' parameter based on ServiceProfileOptions challengeUrl = QueryHelpers.AddQueryString(challengeUrl, "_profile", Options.GetServiceProfileOptionsString()); // Returns the final URL like: // https://login.pst.oneid.ehealthontario.ca/oidc/authorize?client_id=...&scope=...&redirect_uri=...&aud=...&_profile=... return challengeUrl; }
Explanation: This code takes the basic login URL and tacks on extra query parameters (
aud,_profile) that OneID specifically requires, using values derived from your OneIdAuthenticationOptions. -
Exchanging the Code for Tokens (ASP.NET Core Example): This method prepares and sends the secure backchannel request to OneID's token endpoint.
// From: src/AspNet.Security.OAuth.OneID/OneIdAuthenticationHandler.NetCore.cs protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(OAuthCodeExchangeContext context) { // Prepare the POST request to the Token Endpoint URL from options using var request = new HttpRequestMessage(HttpMethod.Post, Options.TokenEndpoint); // ... Set standard headers ... // Get the 'code_verifier' needed for PKCE security if (!context.Properties.Items.TryGetValue("code_verifier", out var codeVerifierValue)) { throw new InvalidOperationException("code_verifier is missing"); } // Parameters for the token request body var parameters = new Dictionary<string, string> { ["redirect_uri"] = context.RedirectUri, // Must match original request ["grant_type"] = "authorization_code", // Standard grant type ["client_id"] = Options.ClientId, // Your app's ID from options ["code"] = context.Code, // The authorization code received ["code_verifier"] = codeVerifierValue ?? string.Empty // PKCE verifier }; // Set the request body request.Content = new FormUrlEncodedContent(parameters.AsEnumerable()); // Send the request using the configured Backchannel (which uses the certificate) using var response = await Backchannel.SendAsync(request, Context.RequestAborted).ConfigureAwait(false); // Check for errors and parse the successful JSON response containing tokens if (!response.IsSuccessStatusCode) { // ... Handle error ... var errorBody = await response.Content.ReadAsStringAsync().ConfigureAwait(false); Logger.LogBackchannelFailure(response.StatusCode, response.Headers.ToString(), errorBody); return OAuthTokenResponse.Failed(new OneIdAuthenticationException(...)); } // Parse the JSON response from OneID (contains access_token, id_token, etc.) var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync().ConfigureAwait(false)); return OAuthTokenResponse.Success(payload); }
Explanation: This code constructs the secure request to trade the
codefor tokens. It includes thecode, yourClientId, theredirect_uri, and security parameters likecode_verifier. It then uses theBackchannel(which involves the OneIdAuthenticationBackChannelHandler and your certificate) to send this request and process the response containing the tokens. -
Creating the Ticket (User Identity) (ASP.NET Core Example): After getting tokens, this method validates the ID token, extracts claims, and creates the
ClaimsPrincipal.// From: src/AspNet.Security.OAuth.OneID/OneIdAuthenticationHandler.NetCore.cs protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { // Get the ID token string from the response var idToken = tokens.Response!.RootElement.GetString("id_token"); if (string.IsNullOrWhiteSpace(idToken)) { /* ... error ... */ } // Optional: Validate the ID token's signature and claims if (Options.ValidateTokens) { var validateIdContext = new OneIdValidateIdTokenContext(Context, Scheme, Options, idToken); await Events.ValidateIdToken(validateIdContext).ConfigureAwait(false); } // Extract claims (user info) from the ID token foreach (var claim in ExtractClaimsFromToken(idToken)) { identity.AddClaim(claim); } // Create the final user principal (identity for the app) var principal = new ClaimsPrincipal(identity); // Context for further processing and claim actions var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, tokens.Response!.RootElement); // Optional: Save tokens to session or properties if configured if (Options.SaveTokens /* ... and check TokenSaveOptions ... */) { // Code to save id_token, access_token, refresh_token // Example: context.HttpContext.Session.SetString("id_token", idToken); // Or: SaveToken(properties, idToken, "id_token"); } // Allow for custom claim transformations context.RunClaimActions(); // Allow application code to hook into the ticket creation await Events.CreatingTicket(context).ConfigureAwait(false); // Return the final AuthenticationTicket containing the user identity and properties return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name); }
Explanation: This code takes the tokens received from
ExchangeCodeAsync, focuses on theid_token, validates it (if configured), pulls out user details (ExtractClaimsFromToken), builds the user's identity (ClaimsPrincipal), saves the tokens (ifSaveTokensis true), and wraps everything in anAuthenticationTicketfor the framework to use for signing the user in.
The OneIdAuthenticationHandler is the unsung hero of the OneID login process within the middleware. Acting like a meticulous security guard, it manages the entire flow: redirecting the user to OneID, handling the callback, securely exchanging the authorization code for tokens via the backchannel, validating those tokens, and finally creating the user's identity for your application. You don't configure it directly, but understanding its crucial role helps demystify how OneID login works behind the scenes after you've set up the OneIdAuthenticationOptions and enabled the middleware with OneIdAuthenticationExtensions.
One particularly sensitive part of the handler's job is the secure backchannel communication where it exchanges the code for tokens using your application's certificate. How is that secure connection handled? That's the job of the OneIdAuthenticationBackChannelHandler, which we'll explore next.
Next: Chapter 5: OneIdAuthenticationBackChannelHandler
Generated by AI Codebase Knowledge Builder