How to configure JwtBearer with mandatory claim? - asp.net-core

My application logic depends on a claim existing, hence this claim is mandatory and needs to always be present in the token.
I am not interested in a Authorization Policy since policies applies to different users and this is a mandatory claim required to be present in all tokens.
Right now my controllers contains:
private const string MyCustomClaim = "foo";
private string _myCustomClaim;
public override void OnActionExecuting(ActionExecutingContext context)
{
_myCustomClaim = context.HttpContext.User.FindFirst(MyCustomClaim)?.Value;
}
If the field _myCustomClaim is null then things will fail later.
I could add a null check and throw an exception, but it would be better if the Authorization middleware did not authorize the user if the token did not contain the claim.
Is there any way to inform the Authorization middleware that a certain claim is mandatory?

In the Startup.cs file when configuring the authentication middleware handle the OnTokenValidated event.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
const string claimTypeFoo = "foo";
if (!context.Principal.HasClaim(c => c.Type == claimTypeFoo))
{
context.Fail($"The claim '{claimTypeFoo}' is not present in the token.");
}
return Task.CompletedTask;
}
};
});
This could also be done in a class:
File Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.Events = new MyJwtBearerEvents();
});
File MyJwtBearerEvents.cs
public class MyJwtBearerEvents : JwtBearerEvents
{
private const string ClaimTypeFoo = "foo";
public override Task TokenValidated(TokenValidatedContext context)
{
if (!context.Principal.HasClaim(c => c.Type == ClaimTypeFoo))
{
context.Fail($"The claim '{ClaimTypeFoo}' is not present in the token.");
}
return Task.CompletedTask;
}
}

Related

Configuring multiple alternate authentication options with precedence in WebApi

I am unclear on how to achieve multiple alternate authentication options with precedence -
I can successfully separately implement
OAuth (Single Sign On)
API key Authentication (passed as a header)
What I am unclear on - how do I configure it so that if the API key header is present and the API key is valid to process the request; that is if you have a valid API-Key, you do not need the SSO.
If no API-Key is presented then you should expect to pass SSO.
The default should be SSO - hence I have the following configured in my Program.cs
builder.Services
.AddAuthentication(
options =>
{
// If an authentication cookie is present, use it to get authentication information
options.DefaultAuthenticateScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// If authentication is required, and no cookie is present,
// use OAuth to sign in
options.DefaultChallengeScheme = "OAuth";
}
)
.AddCookie(
options =>
{
options.AccessDeniedPath = "/accessdenied";
}
)
.AddOAuth(
"OAuth",
(OAuthOptions options) =>
{
// abstracted out but takes care of claims, etc...
WebApp.Utils.OAuthHelper.Process(options, OAuthConfig);
}
);
But my question is - how do I configure this to say - if API Key Header is present, don't bother with OAuth.
The AuthenticationSchemeOptions class has ForwardDefaultSelector that allows any AuthenticationHandler to forward authentication events to another handler.
So what you can do is make your ApiKeyAuthenticationHandler the detault and make it forward authentication events to OAuth handler if API Key header is not present in the HTTP headers.
For example: Let's say you implement ApiKeyAuthenticationHandler as Follows:
// API Key options
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public string ApiKeyHeaderName { get; set; } = "X-MY-API-KEY";
// Add more options here if needed
}
// API Key Authentication handler
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
// Inject any additional services needed by your key handler here
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
{
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
// Validation logic here
// Check if the header exists
if (!Context.Request.Headers.TryGetValue(Options.ApiKeyHeaderName, out var headerValues))
{
// Header is not present fail
return AuthenticateResult.Fail("Invalid API key.");
}
// Header is present validate user
var user = await GetApiKeyUserAsync(headerValues.First());
if (user == null)
{
return AuthenticateResult.Fail("Invalid API key.");
}
// API Key is valid, generate a ClaimIdentity and Principal for the authenticated user
var identity = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name, user)
}, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
return AuthenticateResult.Success(new AuthenticationTicket(principal, null, Scheme.Name));
}
// Key validation logic here and you can map an API key to a user
private async Task<string?> GetApiKeyUserAsync(string key)
{
// Add your implementation here
if (key == "Alice's Key")
{
return "Alice";
}
if (key == "Bob's Key")
{
return "Bob";
}
return null;
}
}
Now in the startup class call AddAuthentication and make "ApiKey" the default, and add a Selector function that checks the HTTP Headers for the presence of a key, if the key is present, then return null and let the Api key handler handle the authentication, otherwiser forward to "OAuth".
builder.Services
.AddAuthentication(
options =>
{
options.DefaultAuthenticateScheme =
"ApiKey";
}
)
.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>("ApiKey", apiKeyOptions =>
{
// This scheme will forward Authentication to OAUTH if API Key header is not present
apiKeyOptions.ForwardDefaultSelector = (context) =>
{
// Check the HTTP Request for the presence of Header
if (!context.Request.Headers.TryGetValue(apiKeyOptions.ApiKeyHeaderName, out var headerValues))
{
// Header is not present, forward to OAuth
return "OAuth"; // "OAuth" is the scheme name you specified when you called "AddOAuth()
}
// Do not forward the authentication
return null;
};
})
// Add other schemes here

Authorization in .NET Core 3: Order in which requirements are being executed

I am imposing different policies to be satisfied in order to execute certain actions and I'd like to decide the order in which these policies have to be exectued. If a user is not authenticated, I should not check if it has permissions or not.
In startup I have:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers()
.RequireAuthorization("UserIsAuthenticated")
.RequireAuthorization("UserIsRegistered");
});
and then I use the default [Authorize(Roles = "Administrator")] attribute and my [MyAuthorize] attributes.
The order in which these polices should be executed is:
First, UserIsAuthenticated, to check if the user is authenticated.
If they are, it should check UserIsRegistered.
Finally the attributes should be applied.
In my case order matters, because I have
services.AddAuthorization(options => { options.InvokeHandlersAfterFailure = false; });
(if a user is not authenticated, I can't check their claims and it makes no sense to check the following policies).
However, in some cases I've seen that the attributes are being evaluated before the Authentication policies.
Is there a way to impose the order of the requirements?
You could create a custom AuthorizeMultiplePolicyFilter to check these policies manually.Apply the filter to global then it will be excuted before action/controller filters.
public class AuthorizeMultiplePolicyFilter: IAsyncAuthorizationFilter
{
private IAuthorizationService _authorization;
private string[] _policies;
public AuthorizeMultiplePolicyFilter(string[] policies)
{
_policies = policies;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (context.HttpContext.Request.Path.StartsWithSegments("/Account"))
{
return;
}
_authorization = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
foreach (var policy in _policies)
{
var authorized = await _authorization.AuthorizeAsync(context.HttpContext.User, policy);
if (!authorized.Succeeded)
{
if(policy == "UserIsAuthenticated")
{
context.Result = new RedirectResult("/Account/Login");
}
if(policy == "UserIsRegistered")
{
context.Result = new ForbidResult();
}
return;
}
}
}
}
Startup.cs
services.AddControllersWithViews(options =>
options.Filters.Add(new AuthorizeMultiplePolicyFilter(new string[] { "UserIsAuthenticated", "UserIsRegistered" }))
);

TenantId Claim not being returned from IdentityServer4 to Client App

Following on from this question, I ended up using the HttpContext.SignInAsync(string subject, Claim[] claims) overload to pass the selected tenant id as a claim (defined as type "TenantId") after the user selects a Tenant.
I then check for this claim in my custom AccountChooserResponseGenerator class from this question to determine if the user needs to be directed to the Tenant Chooser page or not, as follows:
public override async Task<InteractionResponse> ProcessInteractionAsync(ValidatedAuthorizeRequest request, ConsentResponse consent = null)
{
var response = await base.ProcessInteractionAsync(request, consent);
if (response.IsConsent || response.IsLogin || response.IsError)
return response;
if (!request.Subject.HasClaim(c=> c.Type == "TenantId" && c.Value != "0"))
return new InteractionResponse
{
RedirectUrl = "/Tenant"
};
return new InteractionResponse();
}
The interaction is working and the user gets correctly redirected back to the Client app after selecting a Tenant.
However, on my client, I have the simple:
<dl>
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
</dl>
snippet from the IdentityServer4 quickstarts to show the claims, and sadly, my TenantId claim is not there.
I have allowed for it in the definition of my Client on my IdentityServer setup, as follows:
var client = new Client
{
... other settings here
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
"TenantId"
}
};
What am I missing in order for this TenantId claim to become visible in my Client application?
EDIT:
Based on #d_f's comments, I have now added TentantId to my server's GetIdentityResources(), as follows:
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResources.Phone(),
new IdentityResource("TenantId", new[] {"TenantId"})
};
}
And I have edited the client's startup.ConfigureServices(IServiceCollection services) to request this additional scope, as follows:
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
//other settings not shown
options.Scope.Add("TenantId");
});
And still the only claims displayed on the client by the indicated snippet are:
Edit 2: Fixed!
Finally #RichardGowan's answer worked. And that is because (as brilliantly observed by #AdemCaglin) I was using IdentityServer's AspNetIdentity, which has it's own implementation of IProfileService, which kept dropping my custom TenantId claim, despite ALL these other settings).
So in the end, I could undo all those other settings...I have no mention of the TenantId claim in GetIdentityResources, no mention of it in AllowedScopes in the definition of the Client in my IdSrv, and no mention of it in the configuration of services.AddAuthentication on my client.
You will need to provide and register an implementation of IProfileService to issue your custom claim back to the client:
public class MyProfileService : IProfileService {
public MyProfileService() {
}
public Task GetProfileDataAsync(ProfileDataRequestContext context) {
// Issue custom claim
context.IssuedClaims.Add(context.Subject.Claims.First(c => c.Type ==
"TenantId"));
return Task.CompletedTask;
}
public Task IsActiveAsync(IsActiveContext context) {
context.IsActive = true;
return Task.CompletedTask;
}
}

How to authorize SignalR Core Hub method with JWT

I am using JWT authentication in my ASP.NET Core 2.0 application with OpenIddict.
I am following idea in this thread and calling AuthorizeWithJWT method after SignalR handshake. But now, I do not know what should I set in AuthorizeWithJWT method so I can use [Authorize(Roles="Admin")] for example.
I tried with setting context user, but it is readonly:
public class BaseHub : Hub
{
public async Task AuthorizeWithJWT(string AccessToken)
{
//get user claims from AccesToken
this.Context.User = user; //error User is read only
}
}
And using authorize attribute:
public class VarDesignImportHub : BaseHub
{
[Authorize(Roles = "Admin")]
public async Task Import(string ConnectionString)
{
}
}
I strongly encourage you to continue doing authentication at the handshake level instead of going with a custom and non-standard solution you'd implement at the SignalR level.
Assuming you're using the validation handler, you can force it to retrieve the access token from the query string:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication()
.AddOAuthValidation(options =>
{
options.Events.OnRetrieveToken = context =>
{
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
};
});
}
Or OnMessageReceived if you want to use JWTBearer:
services.AddAuthentication()
.AddJwtBearer(o =>
{
o.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
if (context.Request.Path.ToString().StartsWith("/HUB/"))
context.Token = context.Request.Query["access_token"];
return Task.CompletedTask;
},
};
});
No other change should be required.

ASP.NET Core Windows Authentication and Application Roles

I'm trying to create a fairly simple intranet application that will use Active Directory for authentication, and will use the AspNetRoles table to check if the user is in a certain application role. This app is just an in-house lottery where some users can create events/contests that other users can then submit an entry to the contest. I'm thinking of starting out with 2 basic roles:
Administrator - Can perform CRUD operations on "Event" or
"Contest" entities
Contestant - Can perform GET operations on
"Contest" entities, and can create new "Entry" entities.
Here's where I'm stuck: I've got Windows Authentication working in the sense that from a controller, I can do a User.Identity.Name and see my domain login name. Furthermore, I can verify that an account belongs to a domain group by doing User.IsInRole("Domain Users"). If I want to avoid creating new AD groups for each role in my application (let's say design changes down the road require additional roles), how can I use Authorization on controllers to check against Application Roles?
Here's an example controller I want to use:
[Route("api/[controller]")]
[Authorize(Roles = "Contestant")]
public class EventTypesController : Controller
{
private IRaffleRepository _repository;
private ILogger<EventTypesController> _logger;
public EventTypesController(IRaffleRepository repository, ILogger<EventTypesController> logger)
{
_repository = repository;
_logger = logger;
}
[HttpGet("")]
public IActionResult Get()
{
try
{
var results = _repository.GetAllEventTypes();
return Ok(Mapper.Map<IEnumerable<EventTypeViewModel>>(results));
}
catch (Exception ex)
{
_logger.LogError($"Failed to get all event types: {ex}");
return BadRequest("Error occurred");
}
}
}
In my Startup.cs, in ConfigureServices, I'm wiring up Identity as follows:
services.AddIdentity<RaffleUser, ApplicationRole>()
.AddEntityFrameworkStores<RaffleContext>();
My RaffleUser class is really just the default implementation of IdentityUser:
public class RaffleUser : IdentityUser
{
}
My ApplicationRole class is also just the default implementation of IdentityRole. I also tried seeding some data in a seed class:
if (!await _roleManager.RoleExistsAsync("Administrator"))
{
var adminRole = new ApplicationRole()
{
Name = "Administrator"
};
await _roleManager.CreateAsync(adminRole);
await _context.SaveChangesAsync();
}
if (await _userManager.FindByNameAsync("jmoor") == null)
{
using (var context = new PrincipalContext(ContextType.Domain))
{
var principal = UserPrincipal.FindByIdentity(context, "DOMAIN\\jmoor");
if (principal != null)
{
var user = new RaffleUser()
{
Email = principal.EmailAddress,
UserName = principal.SamAccountName
};
await _userManager.CreateAsync(user);
await _context.SaveChangesAsync();
var adminRole = await _roleManager.FindByNameAsync("Administrator");
if (adminRole != null)
{
await _userManager.AddToRoleAsync(user, adminRole.Name);
await _context.SaveChangesAsync();
}
}
}
}
The data makes it to the tables, but it just seems like at the controller level, I need to convert the authenticated user to an IdentityUser. Do I need some middleware class to do this for me? Would that be the best way to make authorization reusable on all controllers?
First, I ended up creating a custom ClaimsTransformer that returns a ClaimsPrincipal populated with UserClaims and RoleClaims (after refactoring my app, I decided to go with policy-based authorization, and the access claim can be added at either the role or user level):
public async Task<ClaimsPrincipal> TransformAsync(ClaimsTransformationContext context)
{
var identity = (ClaimsIdentity)context.Principal.Identity;
var userName = identity.Name;
if (userName != null)
{
var user = await _userManager.FindByLoginAsync("ActiveDirectory", userName);
if (user != null)
{
identity.AddClaims(await _userManager.GetClaimsAsync(user));
var roles = await _userManager.GetRolesAsync(user);
identity.AddClaims(await GetRoleClaims(roles));
}
}
return context.Principal;
}
private async Task<List<Claim>> GetRoleClaims(IList<string> roles)
{
List<Claim> allRoleClaims = new List<Claim>();
foreach (var role in roles)
{
var rmRole = await _roleManager.FindByNameAsync(role);
var claimsToAdd = await _roleManager.GetClaimsAsync(rmRole);
allRoleClaims.AddRange(claimsToAdd);
}
return allRoleClaims;
}
I wired that up in the Startup.cs:
services.AddScoped<IClaimsTransformer, Services.ClaimsTransformer>();
I also went with Policy-based authorization:
services.AddAuthorization(options =>
{
options.AddPolicy("Administrator", policy => policy.RequireClaim("AccessLevel", "Administrator"));
options.AddPolicy("Project Manager", policy => policy.RequireClaim("AccessLevel", "Project Manager"));
});
So, users or roles can have a claim set with a name of "AccessLevel" and a value specified. To finish everything off, I also created a custom UserManager that just populates the User object with additional details from ActiveDirectory during a CreateAsync.
You need to add a DefaultChallangeScheme to use Windows authentication. This is how i do, but if someone has a better solution i am all ears :)
I use the following setup in my current application.
services.AddIdentity<ApplicationUser, ApplicationRole>()
.AddEntityFrameworkStores<SecurityDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = IISDefaults.AuthenticationScheme;
});
Then i put in my application claims in a transformer.
services.AddTransient<IClaimsTransformation, ClaimsTransformer>();
I hope this will get you in the right direction.