I create a token with IdentityServer4 I copy this example I just modify this
in IdentityServer -> Config
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "TRACEITLMAPI" },
AccessTokenLifetime = 10,
IdentityTokenLifetime = 10
}
};
}
I wanted to test when my token will be expired.
An access token is a self-contained package that contains three parts:
header
payload
signature
The information is in the payload, while the signature ensures the receiver that the payload has not been altered.
Taking the terminology from the documentation into account:
The resource has the information that needs to be protected. The client is the process that wants to access the resource and IdentityServer is the issuer of the access token that the client can use to access the resource.
The token is created by the IdentityServer and the client sends it along with the request in the header to the resource. So it's the resource that needs to validate the token. Being self-contained, means that the resource doesn't have to contact the issuer and can fully trust the token after validation. Luckily, middleware takes care of that, so you don't have to write code for that.
The token should be short-lived, so it's rather seconds, minutes perhaps hours than days. Being short-lived means that the client may want to read the token as well. Not necessarily to validate it, but at least to check whether it's not expired. Because it may have to request a new token.
Now to answer your question, the client can read the token and validate it as follows:
// using System.IdentityModel.Tokens.Jwt;
var tokenHandler = new JwtSecurityTokenHandler();
var jwtSecurityToken = tokenHandler.ReadJwtToken(tokenResponse.AccessToken);
if (jwtSecurityToken.ValidTo < DateTime.UtcNow.AddSeconds(10))
Console.WriteLine("Expired");
Please note that this is local validation in the client. This doesn't mean the token isn't accepted by the resource.
The reason is that there's a configurable tolerance level of accepting the token (clock skew). I believe this is by default five minutes. So while the client may have determined that the token is expired, the resource may still accept it if it's within tolerable range.
But this is not something you can count on. So it's better to refresh the token or request a new token (depending on the flow) when the token is (almost) expired.
Some remarks, since the token can't be altered and there's no need for the resource to contact the issuer, there is no way to revoke the token. Therefor it's necessary to set an expiration time. Please note that a new token doesn't invalidate or revoke other (previous, older) tokens. A Jwt always remains valid until it expires.
And about the statement in your answer, this has nothing to do with the validity of the token. This simply prints a string value:
Console.WriteLine(tokenResponse.AccessToken);
Where tokenResponse is the result of the request (RequestClientCredentialsTokenAsync) and AccessToken is a property in the response object.
Assuming that the token in question is a JWT then the expiry time is contained within the token itself as the exp claim in Unix epoch format. Check out https://jwt.io - it has a useful token visualisation tool on the front page.
If you want to test it against an API that has been configured to accept your access tokens then bear in mind that there's usually a significant (i.e. in the order of minutes) clock skew allowance so even if your token has expired according to an accurate clock the API will continue to accept it. This is by design and the level of clock skew allowed should be configurable.
Related
When using Bearer Token Authentication, SignalR passes the JWT token through the query string. Obviously this bears the risk that the the token gets logged along with the request and thus anybody who can read the log can impersonate by copying the token.
The docs say:
If you have concerns about logging this data with your server logs, you can disable this logging entirely by configuring the Microsoft.AspNetCore.Hosting logger to the Warning level or above (these messages are written at Info level)
At this stage a side question pops into my mind: who guarantees that if the log level is Warning and something bad happens, the log won't still contain the request URL?
The docs continue with:
If you still want to log certain request information, you can write a middleware to log the data you require and filter out the access_token query string value (if present).
I guess the idea is to entirely switch off the default logging of requests and replace it with a custom logging middleware. This does not sound trivial to me. So I was wondering:
Is there any way of hooking into the logger and then customize what's actually being logged?
Or can we leverage the existing HTTP Logging for that? At the first glance it also seems to be a all-or-nothing option in regards to logging of query strings or is there a way of customizing that?
Is there a NuGet package that solves the issue?
How did others solve this problem?
I've resorted to take an approach where the JWT token does need to be sent as part of the query string, as explained here.
To summarize, when set as a cookie, the cookie will automatically be sent as part of the SignalR connection initialization by the browser:
document.cookie = `X-Authorization=${token}; path=/; secure; samesite=strict`; // https://stackoverflow.com/a/48618910/331281
const newConnection = new HubConnectionBuilder()
.withUrl('/background-queue-hub', {
skipNegotiation: true, // to avoid CORS problems (see: https://stackoverflow.com/a/52913505/331281)
transport: HttpTransportType.WebSockets,
})
...
However, this runs the risk of CSWSH. So, server-side we have to check the origin header to mitigate that. It can be done right where the cookie value is copied to the JWT Bearer authentication context:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => // https://stackoverflow.com/a/66485247/331281
{
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// for SignalR authentication we need to read the access token form the cookie
// since we don't want to pass the authentication token through the query string
// as the query string is being logged
if (context.HttpContext.Request.Path.StartsWithSegments(SignalRHubPath))
{
var allowedOrigins = Configuration["SignalR:AllowedOrigins"].Split(';');
if (allowedOrigins.Contains(context.Request.Headers["Origin"].ToString())) // see: https://www.tpeczek.com/2017/07/preventing-cross-site-websocket.html
{
context.Token = context.Request.Cookies["X-Authorization"];
}
else
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
}
}
return Task.CompletedTask;
}
};
});
I have an application that is utilizing Azure AD authentication. I also need to access the Microsoft Graph API for user data. Every example I have found that makes requests to the Graph API is utilizing a cached session token, but since I am using JWT obviously I have no need for storing session state. How can I get a JWT with the proper audience using a JWT with my app as the audience?
For example, here is a request to retrieve a token from the Microsoft Graph AspNetCore Sample:
_userTokenCache = new SessionTokenCache(userId, _memoryCache).GetCacheInstance();
var cca = new ConfidentialClientApplication(
_appId,
_redirectUri,
_credential,
_userTokenCache,
null);
var result = await cca.AcquireTokenSilentAsync(_scopes, cca.Users.First());
return result.AccessToken;
Which utilizes the memory cache to pull the token from a Challenge() redirect sign-in with OpenId Connect cookie. However, since I am using JWT, I already have a bearer token, but with the wrong authority. What do I need to do to acquire a new token that I can use to access the Graph API? I still want the tokens to be authorized for my application id, so I would want a new token that allows me to access the API with server-side rest requests.
Edit: Incorrectly tagged as Azure AD Graph, retagged to Microsoft Graph.
Edit Edit: To clarify, each of the samples I've seen so far is using Session cookies as so:
services.AddAuthentication(sharedOptions => {
sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
However, I am using JWT so I don't have a token cached:
app.UseJwtBearerAuthentication(new JwtBearerOptions {
Authority = $"{instance}{tenant}",
Audience = audience,
SaveToken = true
});
The JWT that I get from requests to login.microsoftonline.com have my application as the audience, whereas the JWT generated by these samples have https://graph.microsoft.com as the audience. So I need to get (I presume at least) a token for this audience using only the token I got from my standard authentication request.
Don't confuse how you manage your token (i.e. token cache) with the tokens themselves. The reason you cache a token is simply so you can request a refreshed token as needed (refresh_token). The refresh token is only provided for certain sceanios (i.e. when using the authorization_code flow and you've requested the offline_access scope).
If you're using a flow without a refresh token (i.e implicit or client_credentials) then you may not need to cache your token. You generally should still cache them since there is an overhead cost to fetching a token from AAD and caching allows you to only retrieve a new token when the existing one expires.
Using DelegateAuthenticationProvider with an existing Token
All that said, it sounds like you've already got a token in hand. Since the entire point of MSAL (which is where ConfidentialClientApplication comes from) it to retrieve and manage tokens for you, I'm not exactly sure why you'd want to do this. I would simply skip MSAL entirely and just use your existing token.
If you're using the Microsoft Graph .NET Client Library you can drop MSAL entirely and simply use your existing token (access_token) via the DelegateAuthenticationProvider:
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) => {
requestMessage.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", token.access_token);
return Task.FromResult(0);
})
);
As for the "proper audience", I'm not sure I understand the context. Your token will need to include scopes for Microsoft Graph but how you define them depends a bit on how you are getting your token.
v1 Endpoint
If you're using the older Azure AD OAUTH endpoint (aka the v1 Endpoint) then you need to configure your Application permissions via the Azure Portal. In order to switch between different APIs (called "Resources") you need to request offline_access and user the refresh_token. Switching involves requesting a refreshed token while passing in a new resource. The resulting token will then work with that resource.
For example, if my default resource is a SharePoint Online instance (https://tenant.sharepoint.com) then I would normally refresh my token with something like this:
private async Task<string> RequestTokenAsync() {
var data = new Dictionary<string, string>();
data.Add("grant_type", "refresh_token");
data.Add("client_id", _clientId);
data.Add("client_secret", _clientSecret);
data.Add("resource", "https://tenant.sharepoint.com");
data.Add("redirect_uri", RedirectUri);
data.Add("refresh_token ", refresh_token);
HttpClient httpClient = new HttpClient();
var response = await httpClient.PostAsync(_tokenUri, new FormUrlEncodedContent(data));
response.EnsureSuccessStatusCode();
var result = await result.Content.ReadAsStringAsync();
}
Now if I want to make a call to Microsoft Graph I will first need to get a token for the https://graph.microsoft.com resource:
private async Task<string> RequestTokenAsync() {
var data = new Dictionary<string, string>();
data.Add("grant_type", "refresh_token");
data.Add("client_id", _clientId);
data.Add("client_secret", _clientSecret);
data.Add("resource", "https://graph.microsoft.com");
data.Add("redirect_uri", RedirectUri);
data.Add("refresh_token ", refresh_token);
HttpClient httpClient = new HttpClient();
var response = await httpClient.PostAsync(_tokenUri, new FormUrlEncodedContent(data));
response.EnsureSuccessStatusCode();
var result = await result.Content.ReadAsStringAsync();
}
Now I have two tokens, one for SharePoint and one for Microsoft Graph. I can switch between resources by simply refreshing the token for the proper resource. I do have to make sure I refresh properly however since if my refresh_token expires before I can replace it, I've lost my credentials entirely.
If this sounds complicated, it is. Generally you need to build some mechanisms to manage which tokens are live, which tokens need to be replaced, etc. This is what that token cache is all about since MSAL/ADAL handle this for you.
v2 Endpoint
The newer v2 Endpoint is far easier to work with. Rather than resources it uses scopes. These scopes include the resource identifier and can be dynamically assigned as needed.
So while in v1 we might assign user.read from Microsoft Graph and user.read from Outlook Rest API, we can now assign both at once in a single token by requesting https://graph.microsoft.com/user.read and https://outlook.office.com/user.read at the same time. This means we get a single token that can be used with either API without getting into the "refresh to switch resource" business from above.
The downside of v2 is that only a limited number of APIs support it at the moment. If you need to work across a number of APIs, you may still be better off using v1 for this reason.
Hope this helps a little.
I am using custom authentication and generating a JWT token.
When I post to any service, any authorization attribute say [Authorize(Roles = "Admin")] is respected and users who don't have this role are getting an authorisation error (good!). This highlights to me that the token generation and posting back works!
However, when a token expires, nothing happens. And it is treated as a valid token while I should be getting some sort of exception, my code is this:
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions()
{
SigningKey = ConfigurationManager.AppSettings["authSigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["authAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["authIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
And:
JwtSecurityToken token = AppServiceLoginHandler.CreateToken(
new Claim[] { new
Claim(JwtRegisteredClaimNames.Sub, assertion["username"]) },
mySigningKey,
myAppURL,
myAppURL,
// Setting very short time to test expiration
TimeSpan.FromSeconds(10));
I am testing locally and I am expecting an error being sent to the client stating an expired token. What am I doing wrong?
There is a 5-minute expiration grace period to account for clock-skew. I'm guessing that you're sending this token sometime after the 10 seconds but before the 5 minute grace period expires. Try waiting for longer than 5 minutes to confirm whether the token has expired.
When a token expires, nothing happens directly. When you first try to use that token, you should get a 401 Unauthorized error for any endpoint using the [Authorize] attribute. That's because the token you are submitting is no longer valid.
I built some JWT middleware for my Asp.net Core REST service based on some examples I found online. I get that the response looks like:
{
"access_token":"...",
"expires_in":3600,
"refresh_token":"???",
"token_type": "Bearer",
}
I understand how to create access_token:
Claim[] claims = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, strUsername),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, dtNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
JwtSecurityToken jwtAccess = new JwtSecurityToken(_options.Issuer, _options.Audience, claims, dtNow.DateTime,
dtNow.DateTime.Add(_options.AccessTokenExpiration), _options.SigningCredentials);
The question is how do I create refresh_token? I have searched high and low and can't find much documentation on it. Basically all every reference says is "its a token stored in a database with a longer TTL that you can create a new access_token from".
So is a refresh_token the same exact thing as access_token with just the longer TTL and the additional step that its validated against the database?
Some of the example JWT responses I've seen seem like the refresh_token is much shorter. My access_token is signed with a certificate using RSA515, so the string is kinda long...
Now personally my refresh tokens are just JWTs with longer TTL and a little more information that help me verify the resource owner.
Take a look at the following article from Auth0 and it support links
https://auth0.com/docs/tokens/refresh_token
It could even be a simple GUID used to map user/client to token where the expiry time is also stored in the database along with the token.
The following example is from the link sited above where they use what looks like a Guid for the refresh token.
So, for instance, assuming there is a user 'test' with password 'test'
and a client 'testclient' with a client secret 'secret', one could
request a new access token/refresh token pair as follows:
$ curl -X POST -H 'Authorization: Basic dGVzdGNsaWVudDpzZWNyZXQ=' -d 'grant_type=password&username=test&password=test' localhost:3000/oauth/token
{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiVlx1MDAxNcKbwoNUwoonbFPCu8KhwrYiLCJpYXQiOjE0NDQyNjI1NDMsImV4cCI6MTQ0NDI2MjU2M30.MldruS1PvZaRZIJR4legQaauQ3_DYKxxP2rFnD37Ip4",
"expires_in":20,
"refresh_token":"fdb8fdbecf1d03ce5e6125c067733c0d51de209c"
}
Once their token has expired they make a call passing the refresh token to get a new access token.
Now we can use the refresh token to get a new access token by hitting
the token endpoint like so:
curl -X POST -H 'Authorization: Basic dGVzdGNsaWVudDpzZWNyZXQ=' -d 'refresh_token=fdb8fdbecf1d03ce5e6125c067733c0d51de209c&grant_type=refresh_token' localhost:3000/oauth/token
{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiVlx1MDAxNcKbwoNUwoonbFPCu8KhwrYiLCJpYXQiOjE0NDQyNjI4NjYsImV4cCI6MTQ0NDI2Mjg4Nn0.Dww7TC-d0teDAgsmKHw7bhF2THNichsE6rVJq9xu_2s",
"expires_in":20,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}
Security Considerations
Refresh Tokens are long-lived. This means when a client gets one from
a server, this token must be stored securely to keep it from being
used by potential attackers, for this reason it is not safe to store
them in the browser. If a Refresh Token is leaked, it may be used to
obtain new Access Tokens (and access protected resources) until it is
either blacklisted or it expires (which may take a long time). Refresh
Tokens must be issued to a single authenticated client to prevent use
of leaked tokens by other parties. Access Tokens must also be kept
secret, but due to its shorter life, security considerations are less
critical.
Let's suppose that we're using OAuth Bearer tokens to secure our API. There is NuGet package with OWIN middleware that will do it for us: https://www.nuget.org/packages/Microsoft.Owin.Security.OAuth.
Everethig looks great, until raises question about access token expiration - we don't want to force use to re-login over and over again. As far as I understand there are three basic ways:
Make Access Token expiration time very big (1 month for instance)
Use OAuth Refresh Tokens that adds much difficulties to both Authentication Server and the user application code (described in following article http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/)
I'm curious is it possible to create the endpoint that will require access token that is about to expire and just answer with new access token to simulate kind of sliding expiration for OAuth Access Tokens?
WARNING! Here is the solution that NO ONE SHOULD USE if you're not 100% sure that your application guarantees (which is impossible) that Access Token can not be compomised (for instance, XSS vulnerability allows to steal Access Token). In this solution once Access Token leaked it can be used to indefinitely prolong the access. OAuth Refresh Tokens solve exactly this problem, limiting access in case of compromising Access Token with very short amount of time, usually about 15 minutes.
[Authorize]
public class RefreshTokenController : ApiController
{
[HttpGet]
public HttpResponseMessage ReissueToken()
{
// just use old identity
var identity = ((ClaimsPrincipal)User).Identity as ClaimsIdentity;
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
DateTimeOffset currentUtc = new SystemClock().UtcNow;
ticket.Properties.IssuedUtc = currentUtc;
ticket.Properties.ExpiresUtc = currentUtc.AddMinutes(30);
string token = Startup.OAuthBearerAuthOptions.AccessTokenFormat.Protect(ticket);
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ObjectContent<object>(new
{
accessToken = token,
expiresIn = (int)((ticket.Properties.ExpiresUtc.Value - ticket.Properties.IssuedUtc.Value).TotalSeconds),
}, Configuration.Formatters.JsonFormatter)
};
}
}