From c5286203b9b2b7fcfee2aed2b03a5923411dc4de Mon Sep 17 00:00:00 2001 From: Adam Reed Date: Sun, 2 Mar 2025 15:46:26 -0600 Subject: [PATCH] Update AuthenticateRequest.cs feat(auth): Add ASP.NET Core HttpRequest overload for AuthenticateRequest Add method overload to Clerk's AuthenticateRequest class to directly support ASP.NET Core HttpRequest objects without conversion. This improves compatibility with Minimal APIs and Native AOT compilation by: - Eliminating complex request conversion between HttpRequest and HttpRequestMessage - Reducing object allocations for better performance - Maintaining the same security validation logic as the original method - Supporting direct extraction of auth tokens from HttpRequest objects This change allows for cleaner integration with ASP.NET Core Minimal APIs while preserving the existing functionality for HttpRequestMessage scenarios. --- .../BackendAPI/Helpers/AuthenticateRequest.cs | 77 ++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/Clerk/BackendAPI/Helpers/AuthenticateRequest.cs b/src/Clerk/BackendAPI/Helpers/AuthenticateRequest.cs index 10352bb5..2881a875 100644 --- a/src/Clerk/BackendAPI/Helpers/AuthenticateRequest.cs +++ b/src/Clerk/BackendAPI/Helpers/AuthenticateRequest.cs @@ -2,6 +2,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; namespace Clerk.BackendAPI.Helpers.Jwks; @@ -60,6 +61,54 @@ public static async Task AuthenticateRequestAsync( } } + /// + /// Checks if the ASP.NET Core HTTP request is authenticated. + /// First the session token is retrieved from either the __session cookie + /// or the HTTP Authorization header. + /// Then the session token is verified: networklessly if the options.jwtKey + /// is provided, otherwise by fetching the JWKS from Clerk's Backend API. + /// + /// The ASP.NET Core HTTP request + /// The request authentication options + /// The request state + /// WARNING: AuthenticateRequestAsync is applicable in the context of Backend APIs only. + public static async Task AuthenticateRequestAsync( + HttpRequest request, + AuthenticateRequestOptions options) + { + var sessionToken = GetSessionToken(request); + if (sessionToken == null) return RequestState.SignedOut(AuthErrorReason.SESSION_TOKEN_MISSING); + + VerifyTokenOptions verifyTokenOptions; + + if (options.JwtKey != null) + verifyTokenOptions = new VerifyTokenOptions( + jwtKey: options.JwtKey, + audiences: options.Audiences, + authorizedParties: options.AuthorizedParties, + clockSkewInMs: options.ClockSkewInMs + ); + else if (options.SecretKey != null) + verifyTokenOptions = new VerifyTokenOptions( + options.SecretKey, + audiences: options.Audiences, + authorizedParties: options.AuthorizedParties, + clockSkewInMs: options.ClockSkewInMs + ); + else + return RequestState.SignedOut(AuthErrorReason.SECRET_KEY_MISSING); + + try + { + var claims = await VerifyToken.VerifyTokenAsync(sessionToken, verifyTokenOptions); + return RequestState.SignedIn(sessionToken, claims); + } + catch (TokenVerificationException e) + { + return RequestState.SignedOut(e.Reason); + } + } + /// /// Retrieve token from __session cookie or Authorization header. /// @@ -90,4 +139,30 @@ public static async Task AuthenticateRequestAsync( return null; } -} \ No newline at end of file + + /// + /// Retrieve token from __session cookie or Authorization header. + /// + /// The ASP.NET Core HTTP request + /// The session token, if present + private static string? GetSessionToken(HttpRequest request) + { + // Check for Authorization header + if (request.Headers.TryGetValue("Authorization", out var authValues)) + { + var authHeader = authValues.ToString(); + if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ")) + { + return authHeader.Substring("Bearer ".Length).Trim(); + } + } + + // Check for __session cookie + if (request.Cookies.TryGetValue(SESSION_COOKIE_NAME, out var sessionCookie)) + { + return sessionCookie; + } + + return null; + } +}