Least privilege Identity Server not working properly on Azure

I am trying to implement an architecture that follows the OAUTH2 / OIDC protocol. For this, I have STS (Identity Server v3 at least privilege), an ASP.NET WebApi application, and ASP.NET MVC for the client. My goal was to have the STS and REST service hosted on Azure, so different clients can use them as public services. So far, so good. Everything seemed to work smoothly and perfectly, before I decided to add a new client that uses one of the redirection streams - a stream of authorization code. I wanted to use the token update option that it offers. I wanted to use short access tokens (10 minutes) for this client and force it to use the update token to get new tokens. Here's how it all looks in code:

STS:

new Client { ClientId = "tripgalleryauthcode", ClientName = "Trip Gallery (Authorization Code)", Flow = Flows.AuthorizationCode, AllowAccessToAllScopes = true, RequireConsent = false, RedirectUris = new List<string> { Tripgallery.Constants.TripgalleryMvcAuthCodePostLogoutCallback }, ClientSecrets = new List<Secret>() { new Secret(Tripgallery.Constants.TripgalleryClientSecret.Sha256()) }, // refresh token options AccessTokenType = AccessTokenType.Jwt, AccessTokenLifetime = 600, RefreshTokenUsage = TokenUsage.OneTimeOnly, // Every time generates new refresh token. Not only access token. RefreshTokenExpiration = TokenExpiration.Sliding, SlidingRefreshTokenLifetime = 1296000, PostLogoutRedirectUris = new List<string>() { Tripgallery.Constants.TripgalleryPostLogoutCallback } } 

Mvc Application (Client):

 private ObjectCache _cache; private readonly string tokensCacheKey = "Tokens"; public HomeController() { _cache = MemoryCache.Default; } // GET: Home public ActionResult Index() { var authorizeRequest = new AuthorizeRequest(Constants.BoongalooSTSAuthorizationEndpoint); var state = HttpContext.Request.Url.OriginalString; var url = authorizeRequest.CreateAuthorizeUrl( "tripgalleryauthcode", "code", "openid profile address tripgallerymanagement offline_access", Constants.TripgalleryMvcAuthCodePostLogoutCallback, state); HttpContext.Response.Redirect(url); return null; } public async Task<ActionResult> StsCallBackForAuthCodeClient() { var authCode = Request.QueryString["code"]; var client = new TokenClient( Constants.TripgallerySTSTokenEndpoint, "tripgalleryauthcode", Constants.TripgalleryClientSecret ); var tokenResponse = await client.RequestAuthorizationCodeAsync( authCode, Constants.TripgalleryMvcAuthCodePostLogoutCallback ); this._cache[this.tokensCacheKey] = new TokenModel() { AccessToken = tokenResponse.AccessToken, IdToken = tokenResponse.IdentityToken, RefreshToken = tokenResponse.RefreshToken, AccessTokenExpiresAt = DateTime.Parse(DateTime.Now.AddSeconds(tokenResponse.ExpiresIn).ToString(CultureInfo.InvariantCulture)) }; return View(); } public ActionResult StartCallingWebApi() { var timer = new Timer(async (e) => { var cachedStuff = this._cache.Get(this.tokensCacheKey) as TokenModel; await ExecuteWebApiCall(cachedStuff); }, null, 0, Convert.ToInt32(TimeSpan.FromMinutes(20).TotalMilliseconds)); return null; } private async Task ExecuteWebApiCall(TokenModel cachedStuff) { // Ensure that access token expires in more than one minute if (cachedStuff != null && cachedStuff.AccessTokenExpiresAt > DateTime.Now.AddMinutes(1)) { await MakeValidApiCall(cachedStuff); } else { // Use the refresh token to get a new access token, id token and refresh token var client = new TokenClient( Constants.TripgallerySTSTokenEndpoint, "tripgalleryauthcode", Constants.TripgalleryClientSecret ); if (cachedStuff != null) { var newTokens = await client.RequestRefreshTokenAsync(cachedStuff.RefreshToken); var value = new TokenModel() { AccessToken = newTokens.AccessToken, IdToken = newTokens.IdentityToken, RefreshToken = newTokens.RefreshToken, AccessTokenExpiresAt = DateTime.Parse( DateTime.Now.AddSeconds(newTokens.ExpiresIn).ToString(CultureInfo.InvariantCulture)) }; this._cache.Set(this.tokensCacheKey, (object)value, new CacheItemPolicy()); await MakeValidApiCall(value); } } } 

The problem is that if I have an STS hosted on Azure, for some reason, if I decide to use the update token 20 or more minutes after the access token expires, I get an error. It doesnโ€™t matter that the refresh token update time is 15 days.

enter image description here

This is the log generated by STS:

 w3wp.exe Warning: 0 : 2017-04-06 12:01:21.456 +00:00 [Warning] AuthorizationCodeStore not configured - falling back to InMemory w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] TokenHandleStore not configured - falling back to InMemory w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] ConsentStore not configured - falling back to InMemory w3wp.exe Warning: 0 : 2017-04-06 12:01:21.512 +00:00 [Warning] RefreshTokenStore not configured - falling back to InMemory w3wp.exe Information: 0 : 2017-04-06 12:01:22.371 +00:00 [Information] Start token request w3wp.exe Information: 0 : 2017-04-06 12:01:22.418 +00:00 [Information] Client secret id found: "tripgalleryauthcode" w3wp.exe Information: 0 : 2017-04-06 12:01:22.418 +00:00 [Information] Client validation success w3wp.exe Information: 0 : 2017-04-06 12:01:22.418 +00:00 [Information] Start token request validation w3wp.exe Information: 0 : 2017-04-06 12:01:22.433 +00:00 [Information] Start validation of refresh token request w3wp.exe Warning: 0 : 2017-04-06 12:01:22.574 +00:00 [Warning] "Refresh token is invalid" "{ \"ClientId\": \"tripgalleryauthcode\", \"ClientName\": \"Trip Gallery (Authorization Code)\", \"GrantType\": \"refresh_token\", \"RefreshToken\": \"140cfb19405a6a4cbace29646751194a\", \"Raw\": { \"grant_type\": \"refresh_token\", \"refresh_token\": \"140cfb19405a6a4cbace29646751194a\" } }" w3wp.exe Information: 0 : 2017-04-06 12:01:22.590 +00:00 [Information] End token request w3wp.exe Information: 0 : 2017-04-06 12:01:22.590 +00:00 [Information] Returning error: invalid_grant w3wp.exe Information: 0 : 2017-04-06 12:01:29.465 +00:00 [Information] Start discovery request w3wp.exe Information: 0 : 2017-04-06 12:01:29.512 +00:00 [Information] Start key discovery request 

The same case where the STS running on my local machine works as expected. I can get new tokens with an update token.

enter image description here

RESLOVED: The problem really is what Fred Han - MSFT pointed out. I needed to implement a permanent store for my update tokens. It is really easy to achieve. Here is how I did it:

Startup.cs Identity Server :

 var idServerServiceFactory = new IdentityServerServiceFactory() .UseInMemoryClients(Clients.Get()) .UseInMemoryScopes(Scopes.Get()); //... // use custom service for tokens maintainance var customRefreshTokenStore = new CustomRefreshTokenStore(); idServerServiceFactory.RefreshTokenStore = new Registration<IRefreshTokenStore>(resolver => customRefreshTokenStore); var options = new IdentityServerOptions { Factory = idServerServiceFactory, // ..... } idsrvApp.UseIdentityServer(options); 

CustomRefreshTokenStore.cs

 public class CustomRefreshTokenStore : IRefreshTokenStore { public Task StoreAsync(string key, RefreshToken value) { // code that uses persitant storage mechanism } public Task<RefreshToken> GetAsync(string key) { // code that uses persitant storage mechanism } public Task RemoveAsync(string key) { // code that uses persitant storage mechanism } public Task<IEnumerable<ITokenMetadata>> GetAllAsync(string subject) { // code that uses persitant storage mechanism } public Task RevokeAsync(string subject, string client) { // code that uses persitant storage mechanism } } 
+2
source share
1 answer

w3wp.exe Attention: 0: 2017-04-06 12: 01: 21.456 +00: 00 [Warning] AuthorizationCodeStore not configured - return to InMemory

w3wp.exe Warning: 0: 2017-04-06 12: 01: 21.512 +00: 00 [Warning] TokenHandleStore not configured - return to InMemory

w3wp.exe Warning: 0: 2017-04-06 12: 01: 21.512 +00: 00 [Warning] ConsentStore is not configured - return to InMemory

w3wp.exe Warning: 0: 2017-04-06 12: 01: 21.512 +00: 00 [Warning] RefreshTokenStore not configured - return to InMemory

It seems that you are storing / storing data in memory, which could be the cause of the problem if you host it on the Azure website with multiple instances behind a load balancer. You can try to save the data in another data store instead of the store in memory.

_cache = MemoryCache.Default;

In addition, you save and retrieve tokensCacheKey through memory in the web API application, which will not work in the Azure multi-instance web farm environment. Store data in external storage, such as Azure storage, in a database, or in a Redis cache.

+1
source

Source: https://habr.com/ru/post/1260209/


All Articles