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.
Related
I use several properties like tenant id, client id, client secret, redirect uri and an authorization code generated for a user. I need to get the access and refresh token, but with the API that don't return anything like a refresh token. I need a refresh token additionnally to the access token and the expire in time.
I use this following code:
ConfidentialClientApplicationOptions options = new ConfidentialClientApplicationOptions();
options.ClientId = clientId;
options.TenantId = tenantId;
options.ClientSecret = clientSecret;
options.RedirectUri = redirectUri;
ConfidentialClientApplicationBuilder builder = ConfidentialClientApplicationBuilder.
CreateWithApplicationOptions(options);
IConfidentialClientApplication app = builder.Build();
AcquireTokenByAuthorizationCodeParameterBuilder acquireTokenBuilder =
app.AcquireTokenByAuthorizationCode(ServiceConstants.ALL_SCOPE_AUTHORIZATIONS.Split(' '), authorizationCode);
AuthenticationResult result = await acquireTokenBuilder.ExecuteAsync();
string accessToken = result.AccessToken;
// NO string refreshToken = result.RefreshToken
Its very strange because in several example, I see the RefreshToken available in AuthenticationResult, but not in mine. Do you know why ? And how I can get the refresh token plz ?
Because after that I will need to refresh the access token when will expire and I only have the access token, tenant id, client id, secret (or certificate) and redirect uri. BTW How to regenerate it after access token expiration ?
thank a lot and best regards
Adrien
You need to check what is passed as ServiceConstants.ALL_SCOPE_AUTHORIZATIONS in both /authorize and /token requests. The list of scopes should contain offline_access scope as it tells Azure that your application will need a refresh token for extended access to resources.
The refresh token will have a longer lifetime than the access token, therefore whenever your access token expires you will be able to call the /token endpoint again providing the previously received refresh token and using the parameter grant_type=refresh_token.
I tried to reproduce the same in my environment and got the results like below:
I created an Azure AD Application and added API permissions:
Note that: To get refresh token make sure to grant offline_access API permission in your Azure AD Application and include it in the scope while generating access token.
I generated access and refresh token using below parameters in Postman:
GET https://login.microsoftonline.com/TenantId/oauth2/v2.0/token
client_id:ClientID
client_secret:ClientSecret
scope:https://graph.microsoft.com/.default offline_access
grant_type:authorization_code
redirect_uri:RedirectUri
code:code
To get this in your code you can include the below line:
refreshToken = result.RefreshToken
To refresh the access token, I used the parameters like below:
https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
client_id:ClientId
grant_type:refresh_token
refresh_token:refreshtoken
client_secret:ClientSecret
Sample Code:
AzureADApp.AcquireTokenByRefreshToken(RefreshToken, scope) .ExecuteAsync();
var refreshedAccessToken = result.AccessToken;
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.
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.
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)
};
}
}
I wonder if it is possible to get a permanent access token for personal use on Reddit?
It will only be me using the App.
For users, the access token expires after 1 hour.
My using the below information that I have about my client-id and secret, I put up a start attempt of trying to get an access token. (MessageBox show "Error 401")
If a user will get a token, one have to click "Allow" in the browser. Very well described here. https://github.com/reddit/reddit/wiki/OAuth2
This it NOT what I am after. I am after for, personal use, an access token only through code. Is this possible?
String requestUrl = "https://ssl.reddit.com/api/v1/access_token";
RestSharp.RestClient rc = new RestSharp.RestClient();
RestSharp.RestRequest request = new RestSharp.RestRequest(requestUrl, RestSharp.Method.POST);
request.AddHeader("Content-Type", "application/json");
//request.AddHeader("Authorization", ""); //???
request.AddHeader("x-li-format", "json");
request.AddParameter("client_id", "abcdefg");
request.AddParameter("client_secret", "abc123-456");
request.AddParameter("grant_type", "abc123-456");
request.AddParameter("scope", "identity");
request.AddParameter("state", "adhasegw"); //whatever value
request.AddParameter("duration", "permanent");
request.AddParameter("redirect_uri", "http://mywebsite.co");
request.RequestFormat = RestSharp.DataFormat.Json;
RestSharp.RestResponse restResponse = (RestSharp.RestResponse)rc.Execute(request);
RestSharp.ResponseStatus responseStatus = restResponse.ResponseStatus;
MessageBox.Show(restResponse.Content.ToString() + "," + responseStatus.ToString());
As of right now, you cannot retrieve a permanent access token. You have 2 options that come close.
The first is to request a "refresh" token when using the standard OAuth flow. That's what you're doing by sending "duration" as "permanent" in your code. The refresh token can be used to automatically retrieve new 1 hour access tokens without user intervention; the only manual steps are on the initial retrieval of the refresh token.
The second alternative, which applies only when writing a script for personal use, is to use the password grant type. The steps are described in more detail on reddit's "OAuth Quick Start" wiki page, but I'll summarize here:
Create an OAuth client (under https://www.reddit.com/prefs/apps) with type = "script"
Make a request to https://www.reddit.com/api/v1/access_token with POST parameters grant_type=password&username=<USERNAME>&password=<PASSWORD>. Send your client ID and secret as HTTP basic authentication. <USERNAME> must be registered as a developer of the OAuth 2 client ID you send.
A client_id and client_secret can be generated for a reddit account by going to https://www.reddit.com/prefs/apps and creating an app:
The part I have hidden is my client_id.
Then you can use a client like praw to access reddit e.g. with Python:
import praw
r = praw.Reddit(client_id='insert id here',
client_secret='insert secret here',
user_agent='insert user agent')
page = r.subreddit('aww')
top_posts = page.hot(limit=None)
for post in top_posts:
print(post.title, post.ups)
You could use your current browser's user agent, which can be easily found by google searching "what is my user agent" (among other ways).