ASP.NET Core helper library managing user and client access tokens in ASP.NET Core.
important we have moved all active development of this library to this repo. You can read more about the rationale on this blog post.
ASP.NET Core helper library for claims-based identity, OAuth 2.0 and OpenID Connect.
License: Apache License 2.0
I am migrating an API to dotnet core 3.1. I also decided to start using this version of the IdentityModel. Here is the configuration of the API:
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(o =>
{
string authority = Configuration["idsrv_authority"];
o.Authority = authority;
o.Audience = authority + "/resources";
o.RequireHttpsMetadata = true;
});
I have other HTTP/AP/RESTI services hosted externally. I am trying to configure an HttpClient to hit one of them using client credentials. Here is my HttpClient configuration:
services
.AddHttpClient<MyHttpClient>("ng", client => client.BaseAddress = apiAddress);
services
.AddAccessTokenManagement(options =>
options.Client.Clients.Add("identityserver", new ClientCredentialsTokenRequest()
{
Address = $"{authority}/connect/token",
ClientId = clientId,
ClientSecret = clientSecret
})); ;
services.AddClientAccessTokenClient("ng", configureClient: client =>
{
client.BaseAddress = apiAddress;
});
When I try to hit an endpoint I get the following error:
System.InvalidOperationException: 'Unable to load OpenID configuration for configured scheme: Object reference not set to an instance of an object.'
I have figured that if I add the following config and point the default challenge to "oidc" it starts doing the right thing. It tries to request a token at least.
.AddOpenIdConnect("oidc", o =>
{
string authority = Configuration["idsrv_authority"];
o.Authority = authority;
o.ClientId = "a";
o.ClientSecret = "a";
})
However it seems very strange to have such configuration in the startup with AddOpenIdConnect(...)
. I already have specified to the client where is the client credentials address, the clientId and the secret inside the ClientCredentialsTokenRequest
.
I guess I am doing something fundamentally wrong here. My question is what I have to do so that I can get an access token and hit the external API without adding AddOpenIdConnect(...)
?
In ASP.NET Core 3.0 if you use
services.AddTokenManagement()
.ConfigureBackchannelHttpClient(client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
});
it fails with
System.InvalidOperationException:
'The HttpClient factory already has a registered client with the type 'IdentityModel.AspNetCore.TokenEndpointService'.
Client types must be unique.
Consider using inheritance to create multiple unique types with the same API surface.'
Because both AddTokenManagement
ConfigureBackchannelHttpClient
Register services.AddHttpClient<TokenEndpointService>
I've this code:
.ConfigureServices((hostContext, services) =>
{
services.AddAccessTokenManagement(options =>
{
options.Client.Clients.Add("AzureAD", new ClientCredentialsTokenRequest
{
Address = $"https://login.microsoftonline.com/{ten}/oauth2/token",
ClientId = id,
ClientSecret = secret,
GrantType = "client_credentials",
Scope = Scope1,
Parameters =
{
{ "resource", "821eb724-edb8-4dba-b425-3f953250c0ae" }
}
});
});
// custom name is used here:
services.AddClientAccessTokenClient("client", "my-token-client", configureClient: client =>
{
client.BaseAddress = new Uri("https://localhost:5001");
});
services.AddHostedService<Worker>();
});
And the code to send the http get request:
var client = _clientFactory.CreateClient("client");
var response = await client.GetAsync("weatherforecast", stoppingToken);
[12:11:58 DBG] Cache miss for access token for client: my-token-client
Unhandled exception. System.InvalidOperationException: No access token client configuration found for client: my-token-client
at IdentityModel.AspNetCore.AccessTokenManagement.DefaultTokenClientConfigurationService.GetClientCredentialsRequestAsync(String clientName)
at IdentityModel.AspNetCore.AccessTokenManagement.TokenEndpointService.RequestClientAccessToken(String clientName)
at IdentityModel.AspNetCore.AccessTokenManagement.AccessTokenManagementService.<>c__DisplayClass9_0.<<GetClientAccessTokenAsync>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
at IdentityModel.AspNetCore.AccessTokenManagement.AccessTokenManagementService.GetClientAccessTokenAsync(String clientName, Boolean forceRenewal)
at IdentityModel.AspNetCore.AccessTokenManagement.ClientAccessTokenHandler.SetTokenAsync(HttpRequestMessage request, Boolean forceRenewal)
at IdentityModel.AspNetCore.AccessTokenManagement.ClientAccessTokenHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
I have multiple authentication schemes set up, one for OpenID Connect and then a custom one.
services
.AddAuthentication(o => o.DefaultAuthenticateScheme = "customers-cookie")
.AddCookie("customers-cookie", o =>
{
o.Cookie.Name = "login.customers";
})
.AddCookie("oidc-cookie", o =>
{
o.Cookie.Name = "login.admins";
})
.AddOpenIdConnect("oidc", o => o.SignInScheme = "oidc-cookie");
services.AddAccessTokenManagement();
services.AddClientAccessTokenClient("my-client");
The customers
scheme is used by a custom sign-in handler.
When users are authenticated with the oidc-cookie
scheme my-client
works perfectly fine, a bearer token is appended to requests. The client also works when users are not authenticated, no bearer token is appended to requests. But when users are authenticated with the scheme customers
an exception is thrown: No tokens found in cookie properties. SaveTokens must be enabled for automatic token refresh.
. Which is true, there are no tokens in that cookie.
Is it possible to configure so my-client
still works but no bearer token is appended when users are authenticated but there are no tokens? I.e. work the same as when users are not authenticated at all, or why does the code assume that there must be tokens when users are authenticated https://github.com/IdentityModel/IdentityModel.AspNetCore/blob/main/src/AccessTokenManagement/AuthenticationSessionUserTokenStore.cs#L46?
Hiya,
Even though HMAC is listed under the supported algorithms here: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/wiki/Supported-Algorithms
when I try to use HMAC for code (basically just the sample app code against a FusionAuth IdP), I get an exception:
Exceptions caught:
'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.
Algorithm: 'HS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey, KeyId: 'T06oiDoC5J_5OFCoFT-MSMsoTuY', InternalId: 'db1d80f3-2b6c-419c-9b4a-7aa45659770a'.'
is not supported. The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, TokenValidationParameters validationParameters)
If I use an RSA key to sign my JWTs, it works swimmingly.
I googled around a bit and couldn't find a good way to update the SignatureProvider, except this post, which seemed overkill: https://www.scottbrady91.com/C-Sharp/Supporting-Custom-JWT-Signing-Algorithms-in-dotnet-Core
Any suggestions on other things I should google?
Thanks!
Microsoft.AspNetCore.Authentication 2.1.0 is preventing me from using netcoreapp3.1 in my web app.
I am building a client library around IdentityModel.AspNetCore and when I nuget Microsoft.Extensions.DependencyInjection.Abstractions 3.1.2 everything blows up.
Assembly 'MyLib' with identity 'MyLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' uses 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' which has a higher version than referenced assembly 'Microsoft.Extensions.DependencyInjection.Abstractions' with identity 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'
I believe this is because:
IdentityModel.AspNetCore/1.0.0-rc.3 -> Microsoft.AspNetCore.Authentication/2.1.0 -> Microsoft.AspNetCore.Authentication.Abstractions/2.1.0
By default when client request is constructed ClientCredentialStyle set to Body:
//ProtocolRequest.cs
//line 65
public ClientCredentialStyle ClientCredentialStyle { get; set; } = ClientCredentialStyle.PostBody;
According to spec: https://tools.ietf.org/html/rfc6749#section-2.3.1
Including the client credentials in the request-body using the two
parameters is NOT RECOMMENDED and SHOULD be limited to clients unable
to directly utilize the HTTP Basic authentication scheme (or other
password-based HTTP authentication schemes).
Should default setting be ClientCredentialStyle.AuthorizationHeader
?
I'm getting the error "No refresh token found in cookie properties. A refresh token must be requested and SaveTokens must be enabled."
I've looked at the Auth0 docs and it appears they don't return the refresh_token upon refreshing the access token. You can see the response section here: https://auth0.com/docs/tokens/guides/use-refresh-tokens.
It looks like this could be fixed here:
By changing:
await _userTokenStore.StoreTokenAsync(_httpContextAccessor.HttpContext.User, response.AccessToken, response.ExpiresIn, response.RefreshToken);
To:
await _userTokenStore.StoreTokenAsync(_httpContextAccessor.HttpContext.User, response.AccessToken, response.ExpiresIn, response.RefreshToken ?? userToken.RefreshToken);
If this is acceptable to you, I can submit a PR. If you'd prefer it be on some sort of flag, I can look into that too.
Also from the Auth0 docs (https://auth0.com/docs/tokens/concepts/refresh-tokens)
A Refresh Token is a special kind of token that can be used to obtain a renewed access token. You are able to request new access tokens until the Refresh Token is blacklisted. It’s important that refresh tokens are stored securely by the application because they essentially allow a user to remain authenticated forever.
The following code can cause a null reference exception if the userToken returned is null:
public async Task RevokeRefreshTokenAsync()
{
var userToken = await _userTokenStore.GetTokenAsync(_httpContextAccessor.HttpContext.User);
if (!string.IsNullOrEmpty(userToken.RefreshToken))
{
var response = await _tokenEndpointService.RevokeRefreshTokenAsync(userToken.RefreshToken);
if (response.IsError)
{
_logger.LogError("Error revoking refresh token. Error = {error}", response.Error);
}
}
}
I think it can be:
public async Task RevokeRefreshTokenAsync()
{
var userToken = await _userTokenStore.GetTokenAsync(_httpContextAccessor.HttpContext.User);
if (!string.IsNullOrEmpty(userToken?.RefreshToken))
{
var response = await _tokenEndpointService.RevokeRefreshTokenAsync(userToken.RefreshToken);
if (response.IsError)
{
_logger.LogError("Error revoking refresh token. Error = {error}", response.Error);
}
}
}
without any negative side effects.
Kind regards,
Wesley
Sorry to open an issue. This could be a configuration problem, but before I continue, I wanted to ask "Is this even supported?"
I have working code I copied from a .Net Core console app into this Blazor app, to use the Blazor as the front-end.
I used fiddler to confirm the token is retrieved. It is just not included as a bearer token on the request that requires authentication.
None of this is internet-facing. It's all back-end code for internal systems.
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add("app");
builder.Services.AddDistributedMemoryCache();
string devAvatar = "DevAvatar";
builder.Services.AddAccessTokenManagement(options =>
{
options.Client.Clients.Add(devAvatar, new ClientCredentialsTokenRequest
{
Address = "https://devauth/identity/connect/token",
ClientId = "conveyable-restock-api",
ClientSecret = "P^1IVpH@%1^mR7v@",
Scope = "hl:conveyable-restock-api:driver"
});
});
builder.Services.AddHttpClient("DevDisplayApi", configureClient: client =>
{
client.BaseAddress = new Uri("https://devservices/ConveyableRestock/api/Display");
});
builder.Services.AddClientAccessTokenClient("DevDriverApi", tokenClientName: devAvatar, configureClient: client =>
{
client.BaseAddress = new Uri("https://devservices/ConveyableRestock/secureapi/");
});
await builder.Build().RunAsync();
}
This is Excellent library... actually in application if token expired I need to redirect to
Other page (ex.login page) how do o do that?
Trying to think through how to implement my scenario and wanted to get some feedback before even attempting a PR
My IS4 instance is multitenant enabled via endpoint routing with tenant name in the path.
I have an Azure Function that needs to get a client_credentials token with a dynamic tenant. It's processing messages with a tenant name and needs to get the appropriate credentials.
So, right out of the gate, I can't use the DefaultTokenClientConfigurationService. Easy enough. But the issue remains I need to somehow have the context of which tenant I am connecting to.
I think if args were added to ITokenClientConfigurationService.GetClientCredentialsRequestAsync to pass in additional context that would solve my immediate need? Is that something that would be approved?
I could just parse out tenant names by passing it in with the client name, i.e. {tenantName}_{clientName} then split those in the custom ITokenClientConfigurationService, but I could see the need for wanting more stuff in a dynamic configuration like this.
In my instance for UserAccessTokens I can retrieve the multi tenant context via HttpContext, so that is covered, it's just client credentials that I need to address at the moment.
Anyways, looking for feedback :)
Good Afternoon,
I was wondering if anyone else has run into this error: I have a list of objects (classes with several public properties) and I an trying to iterate over them and call a method on my API async-ly and then use ContinueWith to call the API again (different method) to do some db maintenance in order (I am updating a table and when that is done, I am updating a different table). But I keep getting a null reference exception in the GetUserAccessTokenAsync method.
Here is a section of my error log. (I changed the namespace to fit my application - TcrWeb)
2020-02-06 15:36:45.163 -08:00 [Information] Message Added: 2/6/2020 3:36:45 PM, Log ID: 33, AccessToken: eyJhbGciOiJSUzI1NiIsImtpZCI6Ijg1NDc0REJFQkZENUU4MDlFQjhFODZCRTExRUZFNDAwNjA4REM2QTQiLCJ0eXAiOiJhdCtqd3QiLCJ4NXQiOiJoVWROdnJfVjZBbnJqb2EtRWVfa0FHQ054cVEifQ.eyJuYmYiOjE1ODEwMzIxOTgsImV4cCI6MTU4MTAzNTc5OCwiaXNzIjoiaHR0cHM6Ly90Y3Itc3RnLmhjcG52LmNvbSIsImF1ZCI6InRjci1hcGkiLCJjbGllbnRfaWQiOiJ0Y3Itd2ViIiwic3ViIjoiMiIsImF1dGhfdGltZSI6MTU4MTAyNzMxOSwiaWRwIjoibG9jYWwiLCJuYW1lIjoiVGltIFNhdmFnZSIsInByZWZlcnJlZF91c2VybmFtZSI6InRpbW90aHkuc2F2YWdlIiwiZW1haWwiOiJ0c2F2YWdlQGhjcG52LmNvbSIsInJvbGVfbGlzdCI6InN1cGVyX3VzZXIiLCJ1c2VyX2lkIjoiMiIsImZ1bGxfbmFtZSI6IlRpbSBTYXZhZ2UiLCJjbGluaWNfbGlzdCI6IjM0LDM3LDM4LDUwLDUxLDUyLDU1LDU2LDU3LDU4LDU5LDYxLDYzLDY0LDY2LDY5LDcwLDcyLDc0LDc1LDc2LDc3LDc5LDgwLDgxLDgyLDgzLDg3LDg5LDkyLDkzLDk1LDk2LDk3LDEwMSwxMDMsMTc1LDE3OCwxODUsMTg2LDE4OSwxOTAsMTkzLDE5NCwxOTcsMjAyLDIwNCwyMDUsMjA2LDIwNywyMTAsMjI2LDIyNywyMjgsMjI5LDIzMSwyMzMsMjM1LDEsMywxMywxNCwxNiwxOCwxOSwyMCwyMSwyMiwyMywyOSwzMSIsImNsaW5pY19pZCI6IjM0IiwibGFzdF9sb2dpbiI6IjAyLzA2LzIwMjAgMTQ6MTU6MTkiLCJhcF9hcHByb3ZhbF9sZXZlbCI6IjQwIiwidXNlcl9zZXNzaW9uX2lkIjoiMzc2NmRlNmQtZTY0My00NjRmLThiOTQtMTQxMTljMGJlMTNkIiwiYWRkcmVzcyI6IjcwMCBFIFdhcm0gU3ByaW5ncyBSZC4gTGFzIFZlZ2FzLCBOViA4OTExOSIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiJ0aW1vdGh5LnNhdmFnZSIsInNjb3BlIjpbInByb2ZpbGUiLCJvcGVuaWQiLCJ0Y3ItYXBpIiwib2ZmbGluZV9hY2Nlc3MiXSwiYW1yIjpbInB3ZCJdfQ.oujbYTtsZy60XUoHECeP2bEys2wAYwlTHrfjVmLFAKuN9u67ZXnHpV9iNfbS1R69qEisJsrE7ZPh-WIVw73VxiEZyjHMeAbZ2MmEmJKRSUcq0fRwSi70gyQarjgGmir1_EtQvskLGGHyYaLV3Mpuqbv4HTJy7_94P_rrhj7kYu-w_2E1614pK7LeP0PKJbK22jcWdGrXVsqSRPcBCdQP9DQuLlyu5hZD-ecC0jqkQtAAsK_qAQvmy3jiv00z_KNyYLe4nmznPm1tP9PCltQmY6a31j-ZK7gCfMQkh69Rmxaum5o1r7URG7HXem9a-8eKC3ljNW2GIxyOb3z1uuTRjw
Checking if Client is available: True
(THIS LINE IS WHAT MY OBJECT HOLDS)
Encounter Log: Id:33,Campaign:2010,comment:,LogStatus:3,UserId:2,DateOfDischarge:
Error: Object reference not set to an instance of an object. -- StackTrace: at TcrWeb.TokenManagementHttpContextExtensions.GetUserAccessTokenAsync(HttpContext context, Boolean forceRenewal) in C:\Code\WebApps\TcrWeb\TcrWeb\AccessTokenManagement\TokenManagementHttpContextExtensions.cs:line 23
at TcrWeb.UserAccessTokenHandler.SetTokenAsync(HttpRequestMessage request, Boolean forceRenewal) in C:\Code\WebApps\TcrWeb\TcrWeb\AccessTokenManagement\UserAccessTokenHandler.cs:line 54
at TcrWeb.UserAccessTokenHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in C:\Code\WebApps\TcrWeb\TcrWeb\AccessTokenManagement\UserAccessTokenHandler.cs:line 31
at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at TcrWeb.EncounterDbService.MaintainEncounter(EncounterLog log, String accessToken) in C:\Code\WebApps\TcrWeb\TcrWeb\Services\EncounterDbService.cs:line 166
2020-02-06 15:37:21.511 -08:00 [Information] "Cookies" was not authenticated. Failure message: "Unprotect ticket failed"
2020-02-06 15:37:21.515 -08:00 [Information] AuthenticationScheme: "oidc" was challenged.
If you need anything else from me, please don't hesitate to let me know.
Thank you ,
Tim
What are the milestones for a 1.0.0 release? I would love to help if time permits.
Hi there,
you are responding pretty quickly, thanks for that but it seems like you are only processing "open" elements in your workflow as i didn't get a response to my question i've asked after my PR got closed, see #121 (comment).
So once again, perhaps my question is of interest for others too:
When will changes made to the repo be included in a new nuget package? What is your current nuget package release cycle?
Currently i'm working with a git submodule pointing to my fork of your repo but that's not ideal.
So lonG
Daniel
Problem with token client and ClientAssertion method.
This is how token management registered:
services.AddAccessTokenManagement(o =>
{
var tokenGenerator= services.BuildServiceProvider().GetRequiredService<PrivateKeyJwtTokenGenerator>();
o.Client.Clients.Add(new ClientCredentialsTokenRequest
{
Address = TokenEndpoint,
ClientAssertion = new ClientAssertion()
{
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
Value = tokenGenerator.CreateClientAuthJwt()
}
});
});
It works... till token expires. AccessTokenManagement is trying to fetch new token - it is good, but!
ClientAssertion
property is holding same object.
Problem here is that ClientAssertion.Value has same JWT as with initial request. It is not good, as this JWT is created with unique jti
claim, and it means that this jwt supposed to be used only once.
https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
As result token server rejects request for another token.
That would be nice to have option to update user claims (as an example via request to connect/userinfo) when we update access_token (via AddAccessTokenManagement). Or option to update idToken also (because it can have user claims too).
Hello!
Looks like we need to add Distributed Caching manually in .Net Core 2.1 applications according to this line: TokenManagementServiceCollectionExtensions.cs
From my perspective, would be better to support the .Net Core 2.1 version too because it is an LTS version.
For some cases, client_credentials
flow is not enough.
Maybe, for Worker Applications, in the same way as you support ClientCredentialsTokenRequest
, it should be cool to handle PasswordTokenRequest
as well?
Maybe to be more generic, the Clients dictionary in AccessTokenManagementOptions could be a IDictionary<string, T>
where T
is a any TokenRequest
?
Hello,
I am having trouble following the v3 samples and configuring my function app to use the client access token handler.
The FunctionsStartup.Configure
method only provides a reference to the IServiceCollection
so I am not able to call app.UseAuthorization
.
Is there something I am missing or another way around this?
Thanks!
If i run the application which in this repository.
I created one user in the application
i login in application all works
i open different browser and open application url
and then in that browser if i copy past the old browser cookies
after that refresh
user getting access to application without login by just copying the cookies of other user
Hello,
It is not a bug, just question ...
I not clearly understand why ClientTokenRequestDictionary
defined as static?
public class AccessTokenManagementService : IAccessTokenManagementService {
// ...
static readonly ConcurrentDictionary<string, Lazy<Task<string>>> ClientTokenRequestDictionary =
new ConcurrentDictionary<string, Lazy<Task<string>>>();
// ...
public async Task<string> GetClientAccessTokenAsync(string clientName = AccessTokenManagementDefaults.DefaultTokenClientName, bool forceRenewal = false) {
// ...
try
{
return await ClientTokenRequestDictionary.GetOrAdd(clientName, _ => { /* ... */ }).Value;
}
finally
{
ClientTokenRequestDictionary.TryRemove(clientName, out _);
}
}
}
Maybe it is OK .. but is possible case when it returns access token for other user?
Regards
Mike
When calling https://login.microsoftonline.com/{tenant-id}/oauth2/token, I get:
{
"token_type": "Bearer",
"expires_in": "3599",
"ext_expires_in": "3599",
"expires_on": "1595630297",
"not_before": "1595626397",
"resource": "https://xxx.azurewebsites.net",
"access_token": "ey..."
}
And when I use the demo code from https://identitymodel.readthedocs.io/en/latest/aspnetcore/worker.html , all is working ok.
However, what will happen if 1 hour (> 3599 seconds) is passed, and I call the API again, will IdentityModel detect that a get or post to the API fails, and do a new call to aad to get a new access-token?
"var dtRefresh = userToken.Expiration.Subtract(_options.User.RefreshBeforeExpiration);"
throws an exception because the default value of Expiration == DateTime.MinValue of which you cannot subtract anything as this would result in an invalid DateTime object.
In its current implementation, AccessTokenManagementService
suppresses problems encountered when trying to obtain a token. e.g.:
This can make it difficult for consuming developers to identify the cause of failures that occur when performing HTTP requests that should have been (but are not) decorated with authorization headers.
Is there a use case for suppressing these problems instead of throwing exceptions? If so, might it worthwhile to throw by default and add an option to allow suppression when it is actually desirable?
Question.
When configuring AccessTokenManagementService it is necessary to provide token endpoint
services.AddAccessTokenManagement(o =>{
o.Client.Clients.Add(new ClientCredentialsTokenRequest()
{
Address = <TOKEN-END-POINT>
});
});
It could be more convenient to use authority address instead. Internally it could use IDiscoveryCache
to retrieve token endpoint..
Idea is to minimize configuration of the application and use only one config item - authorityUrl
.
All others items can be fetched from .well-know
.
Otherwise need to provide authority - for DiscoveryService, tokenEndpoit for above, etc...
I use a specific web service who generates a JWT access token but does something odd. They are returning the same JWT for each subsequent request until its expired.
This leads to a cache expiration date cannot be in the past exception. Setting CachelifetimeBuffer
to 0 resolves this however loses the benefit of refreshing early for other oauth resources I consume.
System.InvalidOperationException: The absolute expiration value must be in the future.
Besides the open issue/feature request for custom grant_types, I was wondering how to add support for grant_type "password". Which is within the OAuth specification as far as I can tell (I'm not at all an expert)
Looking through the source I think I can see it being partially supported by the IdentityModel project. But I'm not capable of implementing this quickly.
And if I set the GrantType manually it gets overwritten in
HttpClientTokenRequestExtensions.RequestClientCredentialsTokenAsync
As I currently only need to change that, I was wondering if there is a quick way to override that behaviour.
If during registartation in DI user sets GrantType to custom value like:
options.Client.Clients.Add(TokenClientName, new ClientCredentialsTokenRequest
{
Address = "https://demo.identityserver.io/connect/token",
ClientId = "m2m.short",
ClientSecret = "secret",
Scope = "api" // optional
GrantType = "custom_grant",
});
and uses extension method AddClientAccessTokenHandler GrantType is overwritten by client_credentials value.
if in line above would be used extension method RequestTokenAsync instead of RequestClientCredentialsTokenAsync value set by user is preserved.
Is such behaviour be design?
As workaround it is possible to change implementation of ITokenEndpointService to copy of TokenEndpointService
and replace line mentioned above with something likevar grantType = string.IsNullOrEmpty(requestDetails.GrantType) ? OidcConstants.GrantTypes.ClientCredentials : requestDetails.GrantType; return grantType == OidcConstants.GrantTypes.ClientCredentials ? await _httpClient.RequestClientCredentialsTokenAsync(requestDetails, cancellationToken: cancellationToken) : await _httpClient.RequestTokenAsync(requestDetails, cancellationToken: cancellationToken);
Receiving following exception in code:
Unable to resolve service for type 'IdentityModel.AspNetCore.AccessTokenManagement.TokenEndpointService' while attempting to activate 'IdentityModel.AspNetCore.AccessTokenManagement.AccessTokenManagementService'.
Problem resolved by add TokenEndpointService in DI.
services.AddHttpClient<TokenEndpointService>();
Tried to install today but couldn't, didn't realize it wasn't out on NuGet yet. Can we get a pre-release maybe?
Whenever the access token is refreshed, I get an exception "Headers are read-only, response has already started"
There is nothing special about my configuration:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "Auth0";
})
.AddCookie(options =>
{
// ...
})
.AddOpenIdConnect("Auth0", options =>
{
// ...
});
services.AddAccessTokenManagement();
services.AddUserAccessTokenClient("MyApiClient", httpClient =>
{
var uri = Configuration.GetValue<string>("MyApi:Url");
httpClient.BaseAddress = new Uri(uri);
});
Here is there relevant parts of the stacktrace
Error: System.InvalidOperationException: Headers are read-only, response has already started.
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value)
at Microsoft.AspNetCore.Http.ResponseCookies.Append(String key, String value, CookieOptions options)
at Microsoft.AspNetCore.Authentication.Cookies.ChunkingCookieManager.AppendResponseCookie(HttpContext context, String key, String value, CookieOptions options)
at Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler.HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
at Microsoft.AspNetCore.Authentication.AuthenticationService.SignInAsync(HttpContext context, String scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
at IdentityModel.AspNetCore.AccessTokenManagement.AuthenticationSessionUserTokenStore.StoreTokenAsync(ClaimsPrincipal user, String accessToken, Int32 expiresIn, String refreshToken)
at IdentityModel.AspNetCore.AccessTokenManagement.AccessTokenManagementService.RefreshUserAccessTokenAsync()
at IdentityModel.AspNetCore.AccessTokenManagement.AccessTokenManagementService.<GetUserAccessTokenAsync>b__12_1()
at IdentityModel.AspNetCore.AccessTokenManagement.AccessTokenManagementService.GetUserAccessTokenAsync(Boolean forceRenewal)
at Microsoft.AspNetCore.Authentication.TokenManagementHttpContextExtensions.GetUserAccessTokenAsync(HttpContext context, Boolean forceRenewal)
at IdentityModel.AspNetCore.AccessTokenManagement.UserAccessTokenHandler.SetTokenAsync(HttpRequestMessage request, Boolean forceRenewal)
at IdentityModel.AspNetCore.AccessTokenManagement.UserAccessTokenHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
This library perfectly fits our needs for creating http clients with built-in token management. We have tried a poc implementing this package and are very excited about the results.
Our company policy however requires us to only use stable releases of packages.
Is there a rough timing available on when the first stable release can be expected?
In version 4.0 of IdentityModel RefreshTokenDelegatingHandler and AccessTokenDelegatingHandler was removed with comment in release notes “I never was happy with their design, and they also did not work for some common scenarios”.
As I understand right this repo now contains ASP.NET part of the project so I have a question what is the problem with that implementation and what is suggested approach to do that in ASP.NET Core with IdentityModel? I can se ClientAccessTokenHandler but token is retrieved every request (no cache) and I’m wonder about performance.
The ClientAccessTokenHandler is trying to get the IAccessTokenManagementService from the HttpContext, which is not available outside of an http request.
I would expect it to be getting this from it's constructor instead, as there shouldn't be anything that varies between requests, unlike the UserTokenHandler.
Hey there, I'm using this fine preview library in conjunction with ProxyKit to automatically manage the access token for requests to the backend.
The backend itself has mutliple endpoints, some of them beeing callable as anonymous.
Startup isn't really interesting, but for completeness:
services.AddAccessTokenManagement();
services.AddProxy(httpClientBuilder => {
httpClientBuilder.AddUserAccessTokenHandler();
});
Using the proxy will currently fail with a Exception:
System.ArgumentOutOfRangeException: The added or subtracted value results in an un-representable DateTime.
Parameter name: value
at System.DateTime.Subtract(TimeSpan value)
at System.DateTimeOffset.Subtract(TimeSpan value)
at IdentityModel.AspNetCore.AccessTokenManagement.AccessTokenManagementService.GetUserAccessTokenAsync() in C:\local\identity\model\IdentityModel.AspNetCore\src\AccessTokenManagement\AccessTokenManagementService.cs:line 110
at Microsoft.AspNetCore.Authentication.TokenManagementHttpContextExtensions.GetUserAccessTokenAsync(HttpContext context) in C:\local\identity\model\IdentityModel.AspNetCore\src\AccessTokenManagement\TokenManagementHttpContextExtensions.cs:line 26
at IdentityModel.AspNetCore.AccessTokenManagement.UserAccessTokenHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) in C:\local\identity\model\IdentityModel.AspNetCore\src\AccessTokenManagement\UserAccessTokenHandler.cs:line 32
at [...]
This originates from here
Where can we see the release notes for version 3?
Are there any breaking changes?
Hi,
I have a setup where I have configured two clients:
services
.AddTransient<ITokenClientConfigurationService, CustomTokenClientConfigurationService>()
.AddAccessTokenManagement(options =>
{
var defaultRequest = new ClientCredentialsTokenRequest();
Configuration.GetSection("TokenClient").Bind(defaultRequest);
defaultRequest.Scope = "somescope";
options.Client.Clients.Add(TokenClientNames.Default, defaultRequest);
var someOtherRequest = defaultRequest.Clone<ClientCredentialsTokenRequest>();
someOtherRequest.Scope = "someotherscope";
options.Client.Clients.Add(TokenClientNames.SomeOther, someOtherRequest);
});
This fails because the following lines returns false, causing the configuration to be looked up by GetOpenIdConnectSettingsAsync() which in turn requires the ConfigurationManager to be defined on the OpenId options:
Could that line maybe instead be if(_accessTokenManagementOptions.Client.Clients.TryGetValue(clientName, out requestDetails))
and then return the requestDetails if found in the dictionary?
I do not understand where this error is coming from. The App1 receives this message when executing a request towards App3
Error requesting access token for client default. Error = invalid_scope
The token server generated the following error:
IdentityServer4.Validation.TokenRequestValidator[0]
Client cannot request a refresh token in client credentials flow{ clientId = 709038.App1}, details: {
"ClientId": "709038.App1",
"ClientName": "App1",
"GrantType": "client_credentials",
"Scopes": "709038.App2.api 709038.App1.api address email multi-tenant.App3.api offline_access openid profile role",
"Raw": {
"grant_type": "client_credentials",
"client_id": "709038.App1",
"client_secret": "REDACTED"
}
}
The question is: why is it requesting offline_access and thus a refresh token? It indeed does not make sense to do so it seems. Same goes for address email and profile btw. Is there a way to configure the requested scopes specifically for when fetching a Client token?
I've simple configured it like this:
services.AddAccessTokenManagement()
.ConfigureBackchannelHttpClient()
.AddTransientHttpErrorPolicy(policy => policy.WaitAndRetryAsync(new[]
{
TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(2),
TimeSpan.FromSeconds(3)
}));
services.AddHttpClient(HttpClientNames.UserAccessDoNotRetry)
.AddUserAccessTokenHandler();
services.AddHttpClient(HttpClientNames.ClientAccessDoNotRetry)
.AddClientAccessTokenHandler();
UserAccess works perfect
What is a default behavior when we got a HTTP response with 401 status from a resource?
The previous version (AccessTokenDelegatingHandler) tried to renew a token first and do an additional HTTP request to a resource.
But the current version - just sets a token from the store and never expire it (only checks exp
claim)
Hi there,
i have the requirement to implement an ID token refresh. The ID token is automatically refreshed alongside the access token refresh call but the new token returned is not processed hence the refreshed token gets lost. Changing code to process the refreshed token would surely result in a major rewrite of some parts. Do you accept changes to your repo in that direction? I would accept the challenge then 😎
So lonG
Daniel
Hi mate,
im getting this and wondering if its coming from the caching layer of IdentityModel.
It happens after a couple of days of operation - maybe when the token expires?
Message: Input string was not in a correct format.
---> System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(System.ReadOnlySpan`1 str, System.Globalization.NumberStyles options, System.NumberBuffer& number, System.Globalization.NumberFormatInfo info, System.Boolean parseDecimal) at offset 79
at System.Number.ParseInt64(System.ReadOnlySpan`1 value, System.Globalization.NumberStyles options, System.Globalization.NumberFormatInfo numfmt) at offset 22
at IdentityModel.AspNetCore.AccessTokenManagement.ClientAccessTokenCache.<GetAsync>d__4.MoveNext() at offset 245
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at offset 12
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task) at offset 46
at IdentityModel.AspNetCore.AccessTokenManagement.AccessTokenManagementService.<GetClientAccessTokenAsync>d__10.MoveNext() at offset 183
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at offset 12
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task) at offset 46
at IdentityModel.AspNetCore.AccessTokenManagement.ClientAccessTokenHandler.<SetTokenAsync>d__4.MoveNext() at offset 115
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at offset 12
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task) at offset 46
at IdentityModel.AspNetCore.AccessTokenManagement.ClientAccessTokenHandler.<SendAsync>d__3.MoveNext() at offset 137
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at offset 12
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task) at offset 46
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at offset 11
at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<SendAsync>d__2.MoveNext() at offset 190
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at offset 12
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task) at offset 46
at System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1.ConfiguredTaskAwaiter.GetResult() at offset 11
at System.Net.Http.HttpClient.<FinishSendAsyncBuffered>d__62.MoveNext() at offset 141
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at offset 12
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task task) at offset 46
Using .Net core 2.2.
Im running ihostedservice tasks that all fail at the same time (they all use this client (from clientfactory)).
In ClientAccessTokenHandler
doesn't propagate the cancellation token to the underlining implementation that fetches the token from STS.
Problem is that SetTokenAsync
doesn't take cancellationToken
More than happy to send a PR
Current implementation
public static TokenManagementBuilder AddAccessTokenManagement(this IServiceCollection services, Action<AccessTokenManagementOptions> options = null)
Suggestion.
Can it be changed/added another overload with this signature:
public static TokenManagementBuilder AddAccessTokenManagement(this IServiceCollection services, Action<IServiceProvider, AccessTokenManagementOptions> options = null)
Reason:
I need to provide ClientAssertion
value builder and would like to have there DI created object.
services.AddAccessTokenManagement(o =>
{
o.Client.Clients.Add("tokenClient_privateKeyJwt", new ClientCredentialsTokenRequest()
{
Address = "tokenEndpoint",
ClientAssertion = new ClientAssertion()
{
Type = OidcConstants.ClientAssertionTypes.JwtBearer,
Value= TokenGenerator.GetToken() //static method. not good.
// Instead would be nice to have serviceProvider.GetRequiredService<TokenBuilder>.GetToken()
}
});
});
At this moment I use var tokenGenerator= services.BuildServiceProvider().GetRequiredService<TokenBuilder>();
but probably if Service provider given, code would be cleaner.. Something like
public static IHttpClientBuilder AddHttpClient<TClient>(this IServiceCollection services, Action<IServiceProvider, HttpClient> configureClient)
Hi,
I've an Azure Function that doesnt need to be authenticated.
But after I registred the AddAccessTokenManagement and AddClientAccessTokenClient in the IoC, i got the following error:
Here is how i've implemented the function startup code:
If i remove the piece of code, everything backs to works perfectly.
Is there something that i'm missing?
Thanks
I wanted to use device flow or authorization code flow with automatic refresh token management is it possible with this library?
I looked at the code and it seems like this is the case and there's no way you can configure it. IdentityModel helper has required HttpClient extensions though.
Using AddUserAccessTokenHandler
(1) adds handling of refresh token and (2) sets the access token to the http request headers when using a http client .
Refresh token becomes a thing when requesting scope offline_access
. If that scope is not requested refresh is not possible.
Currently AuthenticationSessionUserTokenStore.GetTokenAsync
throws an exception if no refresh token is present. (No refresh token found in cookie properties. A refresh token must be requested and SaveTokens must be enabled
)
Would it not make sense to make refresh token handling optional? So in case that no offline_access is requested handling refresh is not required but the access token is still set to the http request header.
That would have the positive effect that http client registration code is independent on the specific scopes used.
options.Events.OnValidatePrincipal = async e =>
{
var currentToken = await e.HttpContext.GetUserAccessTokenAsync();
if (string.IsNullOrWhiteSpace(currentToken))
{
e.RejectPrincipal();
}
};
In this code snippet, calling GetUserAccessTokenAsync()
always returns null because HttpContext.User.IsAuthenticated
is false. However, e.Principal
is fully populated with the proper principal/claims/etc.
Hi there,
i was looking for a way to refresh access tokens in a Blazor server side apps. The code in this library fits perfectly except the dependencies to HttpContext which is not available in Blazor server side.
My idea is to remove these dependencies where possible and make it compatible with the aforementioned scenario. The AccessTokenManagementService
can then be used in a RevalidatingServerAuthenticationStateProvider
to refresh the access token (and the id token along the way) periodically like
public class BlazorServerAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider
{
protected override Task<bool> ValidateAuthenticationStateAsync(AuthenticationState authenticationState, CancellationToken cancellationToken)
{
...
// gets and refreshes access token based on the "expires_at" claim when necessary,
// id token is refreshed in the process as original authorize request included the open_id scope
var accessToken = _accessTokenManagementService.GetUserAccessTokenAsync(user);
I created PR #120 that includes the necessary changes.
Now one can simply add the following code to a Blazor server side app to refresh the access token right before a HTTP request is made via UserAccessTokenHandler
:
Startup.cs
// add custom user token store to avoid that AddAccessTokenManagement() registers its
// own store based on HttpContext (HttpContext is not available in blazor server side)
services.AddTransient<IUserTokenStore, UserTokenStore>();
var tokenBuilder = services.AddAccessTokenManagement(options =>
{
options.User.Scheme = AzureADB2CDefaults.OpenIdScheme;
options.User.RefreshBeforeExpiration = TimeSpan.FromMinutes(60).Subtract(TimeSpan.FromSeconds(30));
});
tokenBuilder.ConfigureBackchannelHttpClient();
...
services
// add a shared HttpClient ...
.AddHttpClient("MyHttpClient", (serviceProvider, httpClient) =>
{
httpClient.BaseAddress = ...
})
// ... that is injected into the following typed clients ...
.AddTypedClient<...>()
...
// ... supported by a handler that adds the access token to requests
.AddUserAccessTokenHandler();
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.