Authorizing a user based on claims when using a reference token - authorization

I am using a reference token to get the users claims as the JWT token was too long. This works fine from a client controller:
var introspectionClient = new IntrospectionClient(
"http://localhost:5000/connect/introspect",
"api1",
"secret");
var response = await introspectionClient.SendAsync(
new IntrospectionRequest { Token = await HttpContext.GetTokenAsync("access_token") });
ViewBag.Json = Json(response.Json).Value;
return View("json");
As I can use HttpContext to retrieve the access token and exchange it for the users claims at the introspection endpoint.
However from the authorization handler I cannot access the HttpContext to get the access token and the AuthorizationHandlerContextonly contains claims from the id token.
I'm open to all suggestions but it does feel like there should be a way to get the access token in the authorization handler but i could well be wrong.
Thanks in advance for your time.

I do not know if this is the way you should do it. But to answer your question, you can inject the context like this:
//using Microsoft.AspNetCore.Authentication;
public class MyAuthorizationHandler : AuthorizationHandler<MyRequirement>
{
private IHttpContextAccessor _httpContextAccessor;
public MyAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement)
{
var token = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
}
}
You will need to make HandleRequirementAsync async. You don't have to add IHttpContextAccessor in startup to the services. This is already done by Identity.

Related

Can I access my ID Token from an AuthenticationStateProvider in my Blazor client?

I'm trying to access the ID Token provided by Azure AD. I really need to get access to the signature and all of the claims.
I can easily access the AuthenticationStateProvider and then get the User from that. The User property has all of the claims in it, but that seems to be all. I cannot figure out how to get access to the rest of the ID Token.
Here is the code I'm using in the code behind on my blazor page:
[Inject]
AuthenticationStateProvider authenticationState { get; set; }
public async Task<string> GetIDTokenAsync()
{
var authState = await authenticationState.GetAuthenticationStateAsync();
return string.Join(", ", authState.User.Claims.ToList());
}
I'm just returning the list as a string to see what data I'm getting.
Additionally, my bootstrap looks like this:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
});
await builder.Build().RunAsync();
}
}
After I open the website I log in using the RemoteAuthenticatorView component. I need to access the ID Token after logging in. Also I just want to clarify that I am not looking for the access token. I do not ask for any scopes, and therefore there is no access token. My goal here is to get the ID Token and pass it to my API where the API can verify the ID Token and then issue it's own access token.

Customizing the AuthenticationStateProvider in Blazor Server App with Jwt Token Authentication

I've noticed that many developers subclass the AuthenticationStateProvider both in
Blazor Server App and Blazor WebAssembly App wrongly, and more imprtantly for the wrong
reasons.
How to do it correctly and when ?
First off, you do not subclass the AuthenticationStateProvider for the sole purpose of
adding claims to the ClaimPrincipal object. Generally speaking, claims are added after a
user has been authenticated, and if you need to inspect those claims and tranform them, it
should be done somewhere else, not in the AuthenticationStateProvider object. Incidentally, in
Asp.Net Core there are two ways how you can do that, but this merits a question of its own.
I guess that this code sample led many to believe that this is the place to add claims to the ClaimsPrincipal object.
In the current context, implementing Jwt Token Authentication, claims should be added
to the Jwt Token when it is created on the server, and extracted on the client when required,
as for instance, you need the name of the current user. I've noticed that developers save
the name of the user in the local storage, and retrieved it when needed. This is wrong.
You should extract the name of the user from the Jwt Token.
The following code sample describes how to create a custom AuthenticationStateProvider object
whose objective is to retrieve from the local storage a Jwt Token string that has newly added,
parse its content, and create a ClaimsPrincipal object that is served to interested
parties (subscribers to the AuthenticationStateProvider.AuthenticationStateChanged event)
, such as the CascadingAuthenticationState object.
The following code sample demonstrates how you can implement a custom
authenticationstateprovider properly, and for good reason.
public class TokenServerAuthenticationStateProvider :
AuthenticationStateProvider
{
private readonly IJSRuntime _jsRuntime;
public TokenServerAuthenticationStateProvider(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task<string> GetTokenAsync()
=> await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "authToken");
public async Task SetTokenAsync(string token)
{
if (token == null)
{
await _jsRuntime.InvokeAsync<object>("localStorage.removeItem", "authToken");
}
else
{
await _jsRuntime.InvokeAsync<object>("localStorage.setItem", "authToken", token);
}
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await GetTokenAsync();
var identity = string.IsNullOrEmpty(token)
? new ClaimsIdentity()
: new ClaimsIdentity(ServiceExtensions.ParseClaimsFromJwt(token), "jwt");
return new AuthenticationState(new ClaimsPrincipal(identity));
}
}
And here's a code sample residing in the submit button of a Login page that
calls a Web Api endpoint where the user credentials are validated, after which
a Jwt Token is created and passed back to the calling code:
async Task SubmitCredentials()
{
bool lastLoginFailed;
var httpClient = clientFactory.CreateClient();
httpClient.BaseAddress = new Uri("https://localhost:44371/");
var requestJson = JsonSerializer.Serialize(credentials, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, "api/user/login")
{
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
});
var stringContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<LoginResult>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
lastLoginFailed = result.Token == null;
if (!lastLoginFailed)
{
// Success! Store token in underlying auth state service
await TokenProvider.SetTokenAsync(result.Token);
NavigationManager.NavigateTo(ReturnUrl);
}
}
Point to note: TokenProvider is an instance of TokenServerAuthenticationStateProvider.
Its name reflects its functionality: handling the recieved Jwt Token, and providing
the Access Token when requested.
This line of code: TokenProvider.SetTokenAsync(result.Token); passes the Jwt Token
to TokenServerAuthenticationStateProvider.SetTokenAsync in which the token is sored
in the local storage, and then raises AuthenticationStateProvider.AuthenticationStateChanged
event by calling NotifyAuthenticationStateChanged, passing an AuthenticationState object
built from the data contained in the stored Jwt Token.
Note that the GetAuthenticationStateAsync method creates a new ClaimsIdentity object from
the parsed Jwt Token. All the claims added to the newly created ClaimsIdentity object
are retrieved from the Jwt Token. I cannot think of a use case where you have to create
a new claim object and add it to the ClaimsPrincipal object.
The following code is executed when an authenticated user is attempting to access
the FecthData page
#code
{
private WeatherForecast[] forecasts;
protected override async Task OnInitializedAsync()
{
var token = await TokenProvider.GetTokenAsync();
var httpClient = clientFactory.CreateClient();
httpClient.BaseAddress = new Uri("https://localhost:44371/");
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"api/WeatherForecast?startDate={DateTime.Now}"));
var stringContent = await response.Content.ReadAsStringAsync();
forecasts = JsonSerializer.Deserialize<WeatherForecast[]>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
}
Note that the first line of code: var token = await TokenProvider.GetTokenAsync(); retrieves
the Jwt Token stored in the local storage, and add it to the Authorization header of the request.
Hope this helps...
Edit
Note: ServiceExtensions.ParseClaimsFromJwt is a method that gets the Jwt token extracted from the local storage, and parse it into a collection of claims.
Your Startup class should be like this:
public void ConfigureServices(IServiceCollection services)
{
// Code omitted...
services.AddScoped<TokenServerAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(provider => provider.GetRequiredService<TokenServerAuthenticationStateProvider>());
}

How to get the access token in a service in .net core?

I have an application and I want to use a hybrid flow to call an API from my MVC web application. The API is secured and requires an access token (JWT). The code part on the API side is done.
But how can I send my access token from my MVC application to the API in a service? I want to use a "generic" service for this that uses an httpclient.
I know that in controllers you can use
var accessToken = await HttpContext.GetTokenAsync("access_token");
or
var accessToken = await HttpContext.GetUserAccessTokenAsync();
But how can I access the access token in a service?
Tried this too, but didn't work
var accessToken = _httpContextAccessor.HttpContext.Request.Headers[HeaderNames.Authorization];
Side question: how can I set the access token for the httpclient in the constructor of my service? (Using async methods is not possible) so I can't use this:
protected HttpClient HttpClient { get; private set; }
public MyGenericHttpClientService(IHttpContextAccessor httpContextAccessor)
{
var accessToken = await httpContextAccessor.HttpContext.GetTokenAsync("access_token");
HttpClient.SetBearerToken(accessToken);
}
If you are using IHttpClientFactory, you can configure your HttpClient as follows by adding a DelegatingHandler in Startup.cs:
services.AddTransient<TokenHandler>();
services.AddHttpClient().AddHttpMessageHandler<TokenHandler>();
And TokenHandler can be as follows:
public class TokenHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TokenHandler(
IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var token = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
return await base.SendAsync(request, cancellationToken);
}
}
For more information on IHttpClientFactory, you can see this article.

Add claims to a UserPrincipal's request in dotnet core

I've done something similar to this a few years ago but can't find my code for this and can't remember how I did it!
I have a request coming in that is already authenticated but the token doesn't contain a specific claim that I require. Using middleware, I want to read the user id in the token (that already exists) and add an additional claim to the UserPrincipal from an external source based on that user id. NOTE that I don't want to manipulate the token to be sent back, it's just in the context of the api.
I want to do this so that my controller has access to this claim without having to explicitly request it, it should be present to begin with.
I believe I created some middleware that adds the claim but I can't see any examples related to adding a claim, when searching it's more related to updating claims in the actual token sent back which is not what I want.
To add claims to user after authentication, the preferred way for .NET Core 2.x is to use IClaimsTransformation who has a single method TransformAsync(ClaimsPrincipal).
1.Create a CustomClaimsTransformer
public class CustomClaimsTransformer : IClaimsTransformation
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CustomClaimsTransformer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
var accesToken = _httpContextAccessor.HttpContext.Request.Headers["Authorization"];
// get user id from token
//...
//add claims here
((ClaimsIdentity)principal.Identity).AddClaim(new Claim("type-x","value-x"));
return Task.FromResult(principal);
}
}
2.Register it in ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IClaimsTransformation, CustomClaimsTransformer>();
//...
}

OpenIdConnect access_token size and accessing claims server side

I am trying to wrap my head around several concepts here but I don't want this question to be too broad - basically what we are trying to do is use role claims as permissions to lock down our API but I am finding that the access_token is becoming too big.
We are using OpenIddict and ASP.NET Identity 3 on the server side. We have implemented the default AspNetRoleClaims table to store our claims for each role - using them as permissions.
We lock down our API endpoints using custom policy based claims authorization as shown here:
Custom Policy Based Authorization
The main issue I am finding is that our access_token containing our claims is becoming very large. We are attempting to make the ClaimType and Value to be very small in the database to make the claims footprint smaller. We have a basic CRUD type permission scheme, so for each "module" or screen in our SPA client app, there are 4 permissions. The more modules we add to our application, the more the claims are growing in the access_token and our Authorization Bearer header is becoming very large. I am worried about this becoming not very scalable as the app grows.
So the claims are embedded in the access_token and when I hit my endpoint that is locked down with a custom Policy like this...
[Authorize(Policy="MyModuleCanRead")]
[HttpGet]
public IEnumerable<MyViewModel> Get()
I can then access my ASP.NET Identity User and User.Claims in the AuthorizationHandler.
Sorry in advance if this is an obvious question - but I am wondering - in order to get the Custom Policy Based Authorization to work - does it absolutely require the claims to be in either the id_token or the access_token in order to call the handler?
If I remove the claims from the access_token, then my AuthorizationHandler code does not get hit and I cannot access my endpoint that is locked down with my custom Policy.
I am wondering if it is possible to use a custom claims policy but have the actual code that checks for the Claims inside the Authorization handler, so that the claims are not passed with each HTTP request, but are fetched server side from the Authorization cookie or from the database.
* UPDATE *
Pintpoint's answer using Authorization handlers along with the comment on how to remove additional role claims from the cookie achieved just what I was looking for.
In case this helps anyone else - here is the code to override the UserClaimsPrincipalFactory and prevent the role claims from being written to the cookie. (I had many role claims as permissions and the cookie(s) and request headers were becoming too large)
public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
public AppClaimsPrincipalFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
{
}
public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var userId = await UserManager.GetUserIdAsync(user);
var userName = await UserManager.GetUserNameAsync(user);
var id = new ClaimsIdentity(Options.Cookies.ApplicationCookieAuthenticationScheme,
Options.ClaimsIdentity.UserNameClaimType,
Options.ClaimsIdentity.RoleClaimType);
id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, userName));
if (UserManager.SupportsUserSecurityStamp)
{
id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
await UserManager.GetSecurityStampAsync(user)));
}
// code removed that adds the role claims
if (UserManager.SupportsUserClaim)
{
id.AddClaims(await UserManager.GetClaimsAsync(user));
}
return new ClaimsPrincipal(id);
}
}
I am wondering if it is possible to use a custom claims policy but have the actual code that checks for the Claims inside the Authorization handler, so that the claims are not passed with each HTTP request, but are fetched server side from the Authorization cookie or from the database.
It's definitely possible. Here's how you could do that:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("Has-Edit-User-Profiles-Permission", builder =>
{
builder.RequirePermission("Edit-User-Profiles");
});
});
}
}
public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
public PermissionAuthorizationRequirement(string permission)
{
if (string.IsNullOrEmpty(permission))
{
throw new ArgumentException("The permission cannot be null or empty.", nameof(permission));
}
Permission = permission;
}
public string Permission { get; set; }
}
public class PermissionAuthorizationHandler :
AuthorizationHandler<PermissionAuthorizationRequirement>
{
private readonly UserManager<ApplicationUser> _userManager;
public PermissionAuthorizationHandler(UserManager<ApplicationUser> userManager)
{
if (userManager == null)
{
throw new ArgumentNullException(nameof(userManager));
}
_userManager = userManager;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionAuthorizationRequirement requirement)
{
if (context.User == null)
{
return;
}
var user = await _userManager.GetUserAsync(context.User);
if (user == null)
{
return;
}
// Use whatever API you need to ensure the user has the requested permission.
if (await _userManager.IsInRoleAsync(user, requirement.Permission))
{
context.Succeed(requirement);
}
}
}
public static class PermissionAuthorizationExtensions
{
public static AuthorizationPolicyBuilder RequirePermission(
this AuthorizationPolicyBuilder builder, string permission)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (string.IsNullOrEmpty(permission))
{
throw new ArgumentException("The permission cannot be null or empty.", nameof(permission));
}
return builder.AddRequirements(new PermissionAuthorizationRequirement(permission));
}
}