JWT validation in Azure Web App
Why
Goal
Code
Startup
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
void AddPolicies(WebApplicationBuilder builder)
{
var tenant = builder.Configuration.GetValue<string>("TenantId");
var client = builder.Configuration.GetValue<string>("ClientId");
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
opt.Authority = $"https://login.microsoftonline.com/{tenant}/v2.0"; //.well-known/openid-configuration"
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidIssuer = $"https://login.microsoftonline.com/{tenant}/v2.0",
ValidateIssuer = true,
ValidAudience = client,
ValidateAudience = true,
ValidateLifetime = true,
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("EmailPolicy", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.Requirements.Add(new EmailRequirement());
});
options.AddPolicy("AdminPolicy", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.Requirements.Add(new AdminRequirement());
});
});
}
We need a tenant and client id. With those secrets we can define the Authentication based on the JwtBearer schema and set the issuing authority along with the parameters that define what we want to validate.AddPolicies(builder);
app.UseAuthentication();
app.UseAuthorization();
Policies
using Microsoft.AspNetCore.Authorization;
public class EmailRequirement : IAuthorizationRequirement
{
}
Authorization Handler
using Microsoft.AspNetCore.Authorization;
public class PermissionHandler : IAuthorizationHandler
{
...
public Task HandleAsync(AuthorizationHandlerContext context)
{
if (context.Resource is HttpContext httpContext)
{
var jwtMail = securityService.GetJwtMail(httpContext.Request);
if (string.IsNullOrEmpty(jwtMail))
{
context.Fail();
return Task.CompletedTask;
}
var pendingRequirements = context.PendingRequirements.ToList();
foreach (var requirement in pendingRequirements)
{
if (requirement is EmailRequirement)
{
var email = GetEmail(httpContext.Request);
if (!string.IsNullOrEmpty(email) && ...)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
else if (requirement is AdminRequirement)
{
...
}
}
}
return Task.CompletedTask;
}
private static string? GetEmail(HttpRequest request)
{
...
}
}
Getting the email from the JWT
using System.IdentityModel.Tokens.Jwt;
public class SecurityService : ISecurityService
{
...
internal static string? GetJwt(HttpRequest request)
{
try
{
var jwt = request.Headers.Authorization;
if (jwt.Count == 1)
{
var jwtSplit = jwt.ToString().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
return jwtSplit.Last();
}
}
catch
{
return null;
}
return null;
}
public string? GetJwtMail(HttpRequest request)
{
try
{
var jwt = GetJwt(request);
var handler = new JwtSecurityTokenHandler();
var token = handler.ReadJwtToken(jwt);
var claim = token.Claims.FirstOrDefault(c => c.Type.Equals("email", StringComparison.OrdinalIgnoreCase));
if (claim != null)
{
return claim.Value;
}
}
catch
{
return null;
}
return null;
}
}
The JW token is in the Authorization header. We split that string as we don't want the "Bearer" part that precedes the token. We parse the token and get the email from the claims.
builder.Services.AddScoped<IAuthorizationHandler, PermissionHandler>();
[Authorize(Policy = "EmailPolicy")]
And that's all folks.