Refresh Token Expiration time Google calendar - asp.net

Can any one please tell me the expiration time for Refresh token generation through OAuth2. Actually it usually returns 2 parameter access token and refresh token. We used refresh token in order to generate a new access toke if access token get expired. But Google Calendar Version3, i am using refresh token in order to call the calendar API. But here i am getting a problem that token get expires. So can any one please suggest me what can i do when token get expires. According to me there is no expiration time for refresh token. Kindly check the code below for creation of calendar service using refresh token :-
private CalendarService CreateService(string token)
{
KeyValuePair<string, string> credentials = Common.Get3LOCredentials();
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = credentials.Key;
provider.ClientSecret = credentials.Value;
var auth = new Google.Apis.Authentication.OAuth2.OAuth2Authenticator<NativeApplicationClient>(provider, (p) => GetAuthorization(provider, token));
CalendarService service = new CalendarService(new BaseClientService.Initializer()
{
Authenticator = auth,
ApiKey = ConfigurationManager.AppSettings["APIkey"].ToString(),
GZipEnabled = false
});
return service;
}
private static IAuthorizationState GetAuthorization(NativeApplicationClient arg, String Refreshtoken)
{
IAuthorizationState state = new AuthorizationState(new[] { CalendarService.Scopes.Calendar.GetStringValue() });
state.Callback = new Uri(NativeApplicationClient.OutOfBandCallbackUrl);
state.RefreshToken = Refreshtoken;
return state;
}

Refresh Tokens do not expire but they can be revoked. You, the token handler can revoke the token yourself programatically or the end user can revoke the token in their account settings. Make sure you are properly using the refresh token to get a new access token and you're not attempting to use an access token that has expired.
Can you edit your question to show the exact error you are getting?

Related

Do I have the correct token after Postman gets 401 Unauthorized with .Net Core Identity after supplying invalid token?

Not sure why my token is invalid. I fetch it with Postman calling login() and then pasting it into JWT.io it says "invalid signature"
I can paste my secret key into JWT.io and it will validate, but that doesn't seem right because it then changes the token to something different then what my login method returns to the user!
Here is my token and the secret key is "Super secret key". The token becaomes valid when pasted in JWT.io
eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJjaHVjayIsInJvbGUiOlsiTWVtYmVyIiwiQWRtaW4iXSwibmJmIjoxNTc5MTU5NDQ0LCJleHAiOjE1NzkyNDU4NDQsImlhdCI6MTU3OTE1OTQ0NH0.Uau2W66y7Kdj01MQbBeoOXiwVzQJSDMEZnbQc2jt1qUyfdc9N5bsla1VGMPHQPjRAcnKSfY3NwQBhCRE-SHZCQ
Here is where I paste my secret key to validate the token
Then I create a new Postman get query to fetch some values and I use the token sent back from the login. But I get 401 Unauthorized.
I added the token (same one that the login sent me not the changed one after I validated it) in postman like this
I also added "IdentityModelEventSource.ShowPII = true;" to the startup file to show additional debugging and here' what I see in the debug console.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler1
Failed to validate the token.
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey , KeyId:
'.
Exceptions caught:
''.
token: '{"alg":"HS512","typ":"JWT"}.{"nameid":"1","unique_name":"chuck","role":["Member","Admin"],"nbf":1579159444,"exp":1579245844,"iat":1579159444}'.
I can paste my token generation code if asked.
Any explanation in understanding what's going on here would be much appreciated.
Questions - did I miss a step? Do I provide the same token the login sends me back to the authorized controller method or the changed token (tried both)?
Here is the code I use to create the token.
private async Task<string> GenerateJwtToken(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName)
};
var roles = await _userManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8
.GetBytes(_config.GetSection("AppSettings:Token").Value));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(claims),
Expires = DateTime.Now.AddDays(1),
SigningCredentials = creds
};
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
I added
app.Authentication()
to startup and it worked...

Multiple Requests to Refresh Access Token At the Same Time In OIDC

I have a bit of a head-scratcher for updating a refresh tokens in a certain situation with a single page application making multiple api calls at the same time. I have an SPA which has a stack that consists of the following.
Html/JS SPA -> MVC Application -> WebAPI
I make use of the Hybrid flow, when a user logs onto the page I store the id_token the access_token and the refresh_token in the session cookie.
I use a HttpClient which has two DelegatingHandlers to talk to the web API. One of the delegating handlers simply adds the access token to the Authorization header. The other one runs before this and checks the lifetime left on the access token. If the access token has a limited amount of time left the refresh_token is used to get new credentials and save them back to my session.
Here is the code for the OidcTokenRefreshHandler.
public class OidcTokenRefreshHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly OidcTokenRefreshHandlerParams _handlerParams;
public OidcTokenRefreshHandler(IHttpContextAccessor httpContextAccessor, OidcTokenRefreshHandlerParams handlerParams)
{
_httpContextAccessor = httpContextAccessor;
_handlerParams = handlerParams;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
var handler = new JwtSecurityTokenHandler();
var accessTokenObj = handler.ReadJwtToken(accessToken);
var expiry = accessTokenObj.ValidTo;
if (expiry - TimeSpan.FromMinutes(_handlerParams.AccessTokenThresholdTimeInMinutes) < DateTime.UtcNow )
{
await RefreshTokenAsync(cancellationToken);
}
return await base.SendAsync(request, cancellationToken);
}
private async Task RefreshTokenAsync(CancellationToken cancellationToken)
{
var client = new HttpClient();
var discoveryResponse = await client.GetDiscoveryDocumentAsync(_handlerParams.OidcAuthorityUrl, cancellationToken);
if (discoveryResponse.IsError)
{
throw new Exception(discoveryResponse.Error);
}
var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken);
var tokenResponse = await client.RequestRefreshTokenAsync(new RefreshTokenRequest
{
Address = discoveryResponse.TokenEndpoint,
ClientId = _handlerParams.OidcClientId,
ClientSecret = _handlerParams.OidcClientSecret,
RefreshToken = refreshToken
}, cancellationToken);
if (tokenResponse.IsError)
{
throw new Exception(tokenResponse.Error);
}
var tokens = new List<AuthenticationToken>
{
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.IdToken,
Value = tokenResponse.IdentityToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.AccessToken,
Value = tokenResponse.AccessToken
},
new AuthenticationToken
{
Name = OpenIdConnectParameterNames.RefreshToken,
Value = tokenResponse.RefreshToken
}
};
// Sign in the user with a new refresh_token and new access_token.
var info = await _httpContextAccessor.HttpContext.AuthenticateAsync("Cookies");
info.Properties.StoreTokens(tokens);
await _httpContextAccessor.HttpContext.SignInAsync("Cookies", info.Principal, info.Properties);
}
}
The problem is that many calls hit this at roughly the same time. All of these calls will then hit the refresh endpoint at the same time. They will all retrieve new valid access tokens and the application will continue to work. However if 3 requests happen at the same time, three new refresh tokens will be created and only one of these will be valid. Due to the asynchronous nature of the application I have no guarantee that the refresh token stored in my session is actually the latest refresh token. The next time I need to refresh the refresh token may be invalid (and often is).
My thoughts on possible solutions so far.
Lock at the point of checking the access token with a Mutex or similar. However this has the potential to block when it is being used by a different user with a different session (to the best of my knowledge). It also doesn't work if my MVC app is across multiple instances.
Change so the refresh tokens remain valid after use. So it doesn't matter which one of the three gets used.
Any thoughts on which of the above is better or has anyone got a really clever alternative.
Many Thanks!
When all your requests come from the same SPA, the best should be to sync them in the browser and get rid of the problem serverside. Each time your client code requires a token, return a promise. The same promise instance to all requests, so they all get resolved with the only request to the server.
Unfortunately if you proxy all the requests through your local API and never pass your bearer to the SPA, my idea wouldn't work.
But if you keep your refresh token absolutely secure (never send it to the front), I can't see any problem to make it reusable. In that case you can switch on sliding option as excellently described here to perform less renewal requests.

How to get token details like access token,access token expiry time and refresh token etc. on OnAuthorization() method

I want to eject a logic that if access token is expired then generate refresh token on onAuthorization(AuthorizationFilterContext context) method in ASP.NET WEB API Core.
But i am not able to find a way to get token details.Basically how to get token details like expiry , refresh token from AuthorizationFilterContext.
public void OnAuthorization(AuthorizationFilterContext context)
{
var user = context.HttpContext.User;
if (!user.Identity.IsAuthenticated)
{
var test = context.;
..code to get refresh token...
}
}
For token authentication, you could retrive the token from header and then decode the token.
Try code below:
public void OnAuthorization(AuthorizationFilterContext context)
{
//var token = context.HttpContext.GetTokenAsync("access_token").GetAwaiter().GetResult();
var token = context.HttpContext.Request.Headers["Authorization"].FirstOrDefault().Split(" ")[1];
var handler = new JwtSecurityTokenHandler();
var jsonToken = handler.ReadToken(token);
var tokenS = handler.ReadToken(token) as JwtSecurityToken;
}

Aps .net IdentityServer4 authorize

I'm using IdentityServer4 with asp .net identity as authentication point. My APIs/WebApps call identity server to get access token.
Now, how to authorize uses before some action or inside action in my api/app controller?
I can add roles to access token and then in controller (in web api/web app) use AuthorizeAttribute and check if user IsInRole.
But it means that if I will change user roles, he will see it after logout-login (because roles are part of access token) or token has to expire.
I would like to ask identity server about user role(s) each time I need to authorize him to some action (especially to action like modify/delete some data).
Question how?
Or What I have to looking for?
So there's a few possible solutions here:
Make a call to the OIDC UserInfo Endpoint to obtain updated user claims on every request
Lower the cookie lifetime to refresh user info automatically more often
Implement a custom endpoint on IdentityServer for it to post profile change information to a list of subscribed clients (such as your webapp).
Have IdentityServer force single sign out when user profile data is changed
In terms of difficulty to implement, lowering cookie lifetime is the easiest (just change cookie expiration), but it doesn't guarantee up-to-date claims, and it is visible to the user (frequent redirects to IdentityServer, although no login is required if the access token lifetime is still valid)
Having the webapp call the UserInfo Endpoint on each request is the next easiest (see sample below) but has the worst performance implications. Every request will produce a round trip to IdentityServer.
The endpoint / subscriber model would have the lowest performance overhead. UserInfo requests to IdentityServer would ONLY occur when user profile information has actually changed. This would be a bit more complicated to implement:
On your IdentityServer project, you would need to modify changes to profile data, and post an http message to your webapp. The message could simply contain the user ID of the modified user. This message would need to be authenticated somehow to prevent malicious users from voiding legitimate user sessions. You could include a ClientCredentials bearer token for this.
Your webapp would need to receive and authenticate the message. It would need to store the changed user's ID somewhere accessible to the OnValidatePrincipal delegate (through a service in the DI container most likely)
The Cookie OnValidatePrincipal delegate would then inject this local service to check if user information has changed before validating the principal
Code Samples
Get updated UserInfo from endpoint on each call
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get updated UserInfo from IdentityServer
var accessToken = context.Principal.Claims.FirstOrDefault(c => c.Type == "access_token").Value;
var userInfoClient = new UserInfoClient("https://{IdentityServerUrlGoesHere}");
var userInfoResponse = await userInfoClient.GetAsync(accessToken);
// Invalidate Principal if Error Response
if (userInfoResponse.IsError)
{
context.RejectPrincipal();
await context.HttpContext.Authentication.SignOutAsync("NameOfYourCookieAuthSchemeHere");
}
else
{
// Check if claims changed
var claimsChanged = userInfoResponse.Claims.Except(context.Principal.Claims).Any();
if (claimsChanged)
{
// Update claims and replace principal
var newIdentity = context.Principal.Identity as ClaimsIdentity;
newIdentity.AddClaims(userInfoResponse.Claims);
var updatedPrincipal = new ClaimsPrincipal();
context.ReplacePrincipal(updatedPrincipal);
context.ShouldRenew = true;
}
}
}
}
});
Update On Subscribed Change Message from IdentityServer. This example supposes you've created a service (ex IUserChangedService) which stores userIds received at the endpoint from IdentityServer. I don't have samples of the webapp's receiving endpoint or a service.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "NameOfYourCookieAuthSchemeHere",
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = async context =>
{
// Get User ID
var userId = context.Principal.Claims.FirstOrDefault(c => c.Type == "UserIdClaimTypeHere");
var userChangedService = context.HttpContext.RequestServices.GetRequiredService<IUserChangedService>();
var userChanged = await userChangedService.HasUserChanged(userId);
if (userChanged)
{
// Make call to UserInfoEndpoint and update ClaimsPrincipal here. See example above for details
}
}
}
});
The asp.net core docs have an example of this as well, except working with a local database. The approach of wiring to the OnValidatePrincipal method is the same:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie#reacting-to-back-end-changes
Hope this helps!

asp.net core identity extract and save external login tokens and add claims to local identity

I am a stackoverflow noob so please go easy if I am doing this wrong.
I am using asp.net core with the default core identity template (local accounts).
I have accertained how to add claims to user principal when they login locally like so
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginInputModel model)
{
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var user = await _userManager.FindByNameAsync(model.Email);
await _userManager.AddClaimAsync(user, new Claim("your-claim", "your-value"));
And I have figured out how to get claims returned from the external login but I cannot figure out how I would add these before the user principal gets created in the ExternalLoginCallback function
public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null)
{
if (remoteError != null)
{
ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}");
return View(nameof(Login));
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
return RedirectToAction(nameof(Login));
}
else {
// extract claims from external token here
}
// assume add claims to user here before cookie gets created??
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
if (result.Succeeded)
I am assuming the the _signInManager.ExternalLoginSignInAsync function works similar to the local login _signInManager.PasswordSignInAsync in the sense that once it is called, the cookie will be created. But I am just not sure.
Essentially what I am hoping to achieve, is understanding of how to add custom claims into the cookie that gets created regardless of how to user logins in (local or external), and how to persist these claims to the database if required.
I am planning on doing some work where if I have a user login using say google auth, I need to save that access_token from google, because I wish to call into the Google APIs later with it. So I need to be able to include this access_token in with the User Principal that gets created, and I would hope the cookie would have a claim on it I could use at the front end as well.
This might be out of scope on this question but I would also like when the google token expires, for some-how it to use the refresh token and go get a new one, or force the user to relogin.
Any help on this would be super appreciated, I have really tried hard to understand this without posting this question to stackoverflow. I have read many articles with lots of useful info, but does not provide the answers this specific question is asking. So Thank you very much in advance.
cheers
When you use await _userManager.AddClaimAsync(user, new Claim("your-claim", "your-value")); that actually updates the Identity's aspnetuserclaims table.
Whenever you sign in (by using _signInManager.PasswordSignIn or _signInManager.ExternalLoginSignInAsync) the claims from that table are read and added to the cookie that on every request becomes the Principal.
So you probably don't want to be calling the AddClaimAsync method from UserManager on every login.
Regarding external login providers, you have access to the claims when you call (in ExternalCallback and ExternalCallbackConfirmation if you are using the default templates) here:
var info = await _signInManager.GetExternalLoginInfoAsync();
The claims are in info.Principal.Claims.
The access token is not included by default. When it is, it will be here (along with the type and expiry date):
var accessToken = info.AuthenticationTokens.Single(f => f.Name == "access_token").Value;
var tokenType = info.AuthenticationTokens.Single(f => f.Name == "token_type").Value;
var expiryDate = info.AuthenticationTokens.Single(f => f.Name == "expires_at").Value;
To have the access token be included in the AuthenticationTokens collection, when you are configuring the GoogleAuthentication middleware set the SaveTokens flag to true:
app.UseGoogleAuthentication(new GoogleOptions{
ClientId = "...",
ClientSecret = "...",
SaveTokens = true
Now, if you want to have control over which claims go in the cookie you have to "take over" the process of creating the claims principal.
This is done for you when you use _signInManager.PasswordSignIn/ExternalLoginSignInAsync.
So, for example, for ExternalLoginSignInAsync replace:
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false);
With:
var user = await this._userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
var claimsPrincipal = await this._signInManager.CreateUserPrincipalAsync(user);
((ClaimsIdentity)claimsPrincipal.Identity).AddClaim(new Claim("accessToken", info.AuthenticationTokens.Single(t => t.Name == "access_token").Value));
await HttpContext.Authentication.SignInAsync("Identity.Application", claimsPrincipal);
"Identity.Application" is the default cookie name. You can change it in Startup's ConfigureServices method, for example to MainCookie:
services.Configure<IdentityOptions>(options => {
options.Cookies.ApplicationCookie.AuthenticationScheme = "MainCookie";
});
You still need to handle the ExternalCallbackConfirmation action in the AccountController. It will be similar to the example above.

Resources