Skip to content

Commit c0a8463

Browse files
committed
fix/ local dev token validation
1 parent d231f5b commit c0a8463

2 files changed

Lines changed: 62 additions & 2 deletions

File tree

backend/src/CCE.Api.Common/Auth/CceJwtAuthRegistration.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public static IServiceCollection AddCceJwtAuth(
3333
})
3434
.AddScheme<AuthenticationSchemeOptions, DevAuthHandler>(
3535
DevAuthHandler.SchemeName, _ => { });
36+
services.Configure<LocalAuthOptions>(configuration.GetSection(LocalAuthOptions.SectionName));
3637
services.AddHostedService<DevUsersSeeder>();
3738
services.Configure<EntraIdOptions>(configuration.GetSection(EntraIdOptions.SectionName));
3839
services.AddAuthorization();

backend/src/CCE.Api.Common/Auth/DevAuthHandler.cs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
using System.IdentityModel.Tokens.Jwt;
12
using System.Security.Claims;
3+
using System.Text;
24
using System.Text.Encodings.Web;
5+
using CCE.Application.Identity.Auth.Common;
36
using Microsoft.AspNetCore.Authentication;
47
using Microsoft.Extensions.Logging;
58
using Microsoft.Extensions.Options;
9+
using Microsoft.IdentityModel.Tokens;
610

711
namespace CCE.Api.Common.Auth;
812

@@ -51,11 +55,17 @@ public sealed class DevAuthHandler : AuthenticationHandler<AuthenticationSchemeO
5155
["cce-user"] = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-000000000005"),
5256
};
5357

58+
private readonly IOptions<LocalAuthOptions> _localAuthOptions;
59+
5460
public DevAuthHandler(
5561
IOptionsMonitor<AuthenticationSchemeOptions> options,
5662
ILoggerFactory logger,
57-
UrlEncoder encoder)
58-
: base(options, logger, encoder) { }
63+
UrlEncoder encoder,
64+
IOptions<LocalAuthOptions> localAuthOptions)
65+
: base(options, logger, encoder)
66+
{
67+
_localAuthOptions = localAuthOptions;
68+
}
5969

6070
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
6171
{
@@ -96,11 +106,60 @@ protected override Task<AuthenticateResult> HandleAuthenticateAsync()
96106
if (Request.Headers.TryGetValue("Authorization", out var auth))
97107
{
98108
var raw = auth.ToString();
109+
99110
const string devPrefix = "Bearer dev:";
100111
if (raw.StartsWith(devPrefix, StringComparison.OrdinalIgnoreCase))
101112
{
102113
return raw.Substring(devPrefix.Length).Trim();
103114
}
115+
116+
// Fallback: try to decode as a real JWT (e.g. issued by /api/auth/login)
117+
const string bearerPrefix = "Bearer ";
118+
if (raw.StartsWith(bearerPrefix, StringComparison.OrdinalIgnoreCase))
119+
{
120+
var token = raw.Substring(bearerPrefix.Length).Trim();
121+
return TryReadRoleFromJwt(token);
122+
}
123+
}
124+
125+
return null;
126+
}
127+
128+
private string? TryReadRoleFromJwt(string token)
129+
{
130+
try
131+
{
132+
var opts = _localAuthOptions.Value;
133+
var profiles = new[] { opts.External, opts.Internal };
134+
var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };
135+
136+
foreach (var profile in profiles)
137+
{
138+
if (string.IsNullOrWhiteSpace(profile.SigningKey))
139+
continue;
140+
141+
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(profile.SigningKey));
142+
var parameters = new TokenValidationParameters
143+
{
144+
ValidateIssuer = true,
145+
ValidIssuer = profile.Issuer,
146+
ValidateAudience = true,
147+
ValidAudience = profile.Audience,
148+
ValidateIssuerSigningKey = true,
149+
IssuerSigningKey = key,
150+
ValidateLifetime = true,
151+
ClockSkew = TimeSpan.FromMinutes(2),
152+
};
153+
154+
var principal = handler.ValidateToken(token, parameters, out _);
155+
var role = principal.FindFirst("roles")?.Value;
156+
if (!string.IsNullOrEmpty(role))
157+
return role;
158+
}
159+
}
160+
catch (Exception ex)
161+
{
162+
Logger.LogDebug(ex, "Failed to validate JWT in DevAuthHandler fallback");
104163
}
105164

106165
return null;

0 commit comments

Comments
 (0)