| File: DeviceBoundSessionJwtValidator.cs | Web Access |
| Project: src\aspnetcore\src\Security\Authentication\DeviceBoundSessions\src\Microsoft.AspNetCore.Authentication.DeviceBoundSessions.csproj (Microsoft.AspNetCore.Authentication.DeviceBoundSessions) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Text.Json; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; namespace Microsoft.AspNetCore.Authentication.DeviceBoundSessions; /// <summary> /// Validates DBSC proof JWTs (typ: "dbsc+jwt") signed with ES256 or RS256. /// </summary> internal sealed class DeviceBoundSessionJwtValidator { private static readonly JsonWebTokenHandler _tokenHandler = new(); private readonly ILogger<DeviceBoundSessionJwtValidator> _logger; public DeviceBoundSessionJwtValidator(ILogger<DeviceBoundSessionJwtValidator> logger) { _logger = logger; } /// <summary> /// Validates a DBSC proof JWT and extracts its claims. /// </summary> /// <param name="jwt">The raw JWT string.</param> /// <param name="publicKeyJwk">The JWK JSON of the public key to validate against. If null, extracts from JWT header.</param> /// <returns>The parsed result, or null if validation fails.</returns> public async Task<DeviceBoundSessionJwtResult?> ValidateAsync(string jwt, string? publicKeyJwk) { JsonWebToken token; try { token = new JsonWebToken(jwt); } catch (ArgumentException) { _logger.ProofMalformed(); return null; } if (!token.TryGetHeaderValue<string>("typ", out var tokenType) || !string.Equals(tokenType, "dbsc+jwt", StringComparison.Ordinal)) { _logger.ProofWrongType(); return null; } if (!token.TryGetHeaderValue<string>("alg", out var algorithm) || string.IsNullOrEmpty(algorithm)) { _logger.ProofMissingAlgorithm(); return null; } string? jwkJson = publicKeyJwk; if (jwkJson is null) { if (!token.TryGetHeaderValue<JsonElement>("jwk", out var jwkElement)) { _logger.ProofMissingKey(); return null; } jwkJson = jwkElement.GetRawText(); } var securityKey = CreateSecurityKey(jwkJson, algorithm); if (securityKey is null) { _logger.ProofUnsupportedKey(); return null; } var validationResult = await _tokenHandler.ValidateTokenAsync(jwt, new TokenValidationParameters { IssuerSigningKey = securityKey, ValidateAudience = false, ValidateIssuer = false, ValidateIssuerSigningKey = true, ValidateLifetime = false, }); if (!validationResult.IsValid) { _logger.ProofSignatureInvalid(); return null; } token.TryGetPayloadValue("jti", out string? challenge); token.TryGetPayloadValue("authorization", out string? authorization); return new DeviceBoundSessionJwtResult { Algorithm = algorithm, PublicKeyJwk = jwkJson, Challenge = challenge, Authorization = authorization, }; } private static SecurityKey? CreateSecurityKey(string jwkJson, string algorithm) { JsonWebKey jsonWebKey; try { jsonWebKey = new JsonWebKey(jwkJson); } catch (ArgumentException) { return null; } return algorithm switch { DeviceBoundSessionConstants.Es256 when string.Equals(jsonWebKey.Kty, "EC", StringComparison.Ordinal) && string.Equals(jsonWebKey.Crv, "P-256", StringComparison.Ordinal) => jsonWebKey, DeviceBoundSessionConstants.Rs256 when string.Equals(jsonWebKey.Kty, "RSA", StringComparison.Ordinal) => jsonWebKey, _ => null, }; } }