I am having an issue when using the Authorize attribute with a Policy that I'm defining in Startup.cs. I edited my controller to manually check claims. I can see the claims including a scope claim with the correct scopes but when I manually check for that claim/scope it comes back as false. I'm using Azure AD B2C as my identity server and successfully get a validated token.
Here is code from my Startup.cs:
services.AddAuthorization(options =>
{
var policyRead = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireClaim("http://schemas.microsoft.com/identity/claims/scope", "vendor.read")
.Build();
options.AddPolicy("VendorRead", policyRead);
var policyWrite = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.RequireClaim("http://schemas.microsoft.com/identity/claims/scope", "vendor.write")
.Build();
options.AddPolicy("VendorWrite", policyWrite);
});
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = $"{Configuration["AzureAdB2C:Instance"]}/{Configuration["AzureAdB2C:TenantId"]}/{Configuration["AzureAdB2C:SignUpSignInPolicyId"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.RequireHttpsMetadata = true;
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = $"{Configuration["AzureAdB2C:Instance"]}/{Configuration["AzureAdB2C:TenantId"]}/v2.0/",
ValidAudience = Configuration["AzureAdB2C:ClientId"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AzureAdB2C:ClientSecret"]))
};
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed,
OnTokenValidated = TokenValidated
};
});
Here is my controller code where I'm manually checking for claims:
// GET: api/Vendor/5
[HttpGet("{id}")]
public async Task<IActionResult> Get(VendorRequest request)
{
var hasClaim1 = User.HasClaim(c => c.Type == "vendor.read");
var hasClaim2 = User.HasClaim(c => c.Type == "scope");
var hasClaim3 = User.HasClaim(c => c.Type == "scp");
var hasClaim4 = User.HasClaim(c => c.Type == "http://schemas.microsoft.com/identity/claims/scope");
var hasClaim5 = User.HasClaim("http://schemas.microsoft.com/identity/claims/scope", "vendor.read");
var hasClaim7= User.HasClaim("http://schemas.microsoft.com/identity/claims/scope", "vendor.write");
var allowed = await _authorization.AuthorizeAsync(User, "VendorRead");
if (!allowed.Succeeded)
{
return StatusCode(StatusCodes.Status403Forbidden);
}
The only hasClaim that comes back as true is hasClaim4.
Here is what my claims look like:
Any ideas on what I'm doing wrong? I am only trying to get the vendor.read scope to work for now.
Adding claims checks Claim based authorization checks are declarative - the developer embeds them within their code, against a controller or an action within a controller, specifying claims which the current user must possess, and optionally the value the claim must hold to access the requested resource.
The claims-based authorization works by checking if the user has a claim to access an URL. In ASP.NET Core we create policies to implement the Claims-Based Authorization. The policy defines what claims that user must process to satisfy the policy. We apply the policy on the Controller, action method, razor page, etc.
Claims can be created from any user or identity data which can be issued using a trusted identity provider or ASP.NET Core identity. A claim is a name value pair that represents what the subject is, not what the subject can do.
The scope claim is a space delimited list so the RequireClaim()
helper will not work in this case but the more generic RequireAssertion()
will.
Example Scope Claim
"scp": "demo.read demo.write user_impersonation Test-Value"
Sample RequireAssertion()
services.AddAuthorization(options =>
{
options.AddPolicy("ScopeCheck", policyBuilder =>
policyBuilder.RequireAssertion(async handler =>
{
var scopeClaim = handler.User.FindFirst("http://schemas.microsoft.com/identity/claims/scope");
var scopes = scopeClaim?.Value.Split(' ');
var hasScope = scopes?.Where(scope => scope == "demo.write").Any() ?? false;
return hasScope;
}));
});
Sample Controller
[Authorize("ScopeCheck")]
public class SecureController : Controller
{
[HttpGet]
public IActionResult Test()
{
return Ok(new { Message = "You are allowed" });
}
}
Full Sample Project - Sample token
Access Token Scope (RFC 6749 section-3.3)
The value of the scope parameter is expressed as a list of space- delimited, case-sensitive strings. The strings are defined by the authorization server. If the value contains multiple space-delimited strings, their order does not matter, and each string adds an additional access range to the requested scope
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With