I'm running into a very odd issue where the refresh token "disappears" after a few hours on an Azure App Service which hosts my Wep Api project. I've implemented OAuth for my password flow. Our AccessToken expires after 1 hour and our RefreshToken expires after one week.
For some background. This is happening on an Azure App service where i'm hosting my Web Api and a mobile front end is making calls to it (there are more than one users/mobile devices making a call to this app service).
Here's what a sample initial call looks like using /token call:
My grant_type is password. Normally i get back a refresh_token field along with the access_token, token_type and expires_in.
Works fine for the first few hours after i push to the app service then refresh_token disappears. I am truly stumped by this issue.
Here's my CreateAsync Code:
public async Task CreateAsync(AuthenticationTokenCreateContext context)
{
var clientid = context.Ticket.Properties.Dictionary["as:client_id"];
if (string.IsNullOrEmpty(clientid))
{
return;
}
string refreshTokenId = await CreateRefreshTokenId(clientid, context);
if (refreshTokenId != null)
{
context.SetToken(refreshTokenId);
}
else
{
throw new Exception("refresh token could not be created");
}
}
private async Task<string> CreateRefreshTokenId(string clientId, AuthenticationTokenCreateContext context)
{
var ticket = context.Ticket;
var refreshTokenId = Guid.NewGuid().ToString("n");
var refreshTokenLifeTime = ConfigurationManager.AppSettings["as:clientRefreshTokenLifeTime"];
var token = new CreateRefreshTokenDTO
{
RefreshTokenId = refreshTokenId,
ClientId = clientId,
Subject = ticket.Identity.Name,
IssuedUtc = DateTime.UtcNow,
ExpiresUtc = DateTime.UtcNow.AddMinutes(Convert.ToDouble(refreshTokenLifeTime))
};
ticket.Properties.IssuedUtc = token.IssuedUtc;
ticket.Properties.ExpiresUtc = token.ExpiresUtc;
token.ProtectedTicket = context.SerializeTicket();
var result = await createRefreshTokenManager.ManagerRequest(new CreateRefreshTokenRequest
{
RefreshToken = token
});
return result.IsError ? null : refreshTokenId;
}
I've added the exception in the else statement to see if it will throw and exception and it does in fact throw, which leads me to believe that the refreshTokenId is null. I've also added logging to a log table but for whatever reason, when this error is thrown it should save to the DB table (which i've tested locally and works) but on the App server it is not saving to the table. Very perplexing... UPDATE: PLEASE SEE UPDATE BELOW ON WHY NO LOGS WERE SAVING
Then, what is supposed to happen after this is that now that the front end (mobile, in this case) has the access and refresh tokens, when the access token expires, another call is made to /token but with grant_type = refresh_token:

UPDATE
Eventually I was able to reproduce the issue locally through trial and error and waiting for access token to expire (not entirely sure). But in any case, I was able to produce this error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
This error was the reason i was not able to save any logs to the DB.
Im using Castle Windsor as my IoC and EF6. My calls are in this order:
1] Attempt to validate the context. In here i make another await call to a LoginUserManager where I basically get and verify user info
// This is used for validating the context
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
2] CreateAsync for creating access and refresh tokens from Context
public async Task CreateAsync(AuthenticationTokenCreateContext context)
Inside CreateAsync I make an await call CreateOrUpdateRefreshTokenManagerwhich either does an Update if entry exists or a Create. And ultimately make a SaveChanges(). This SaveChanges() is what causes the error If I don't call a SaveChanges() no entry is updated or created in that table. This is odd because in other parts of my code i dont call SaveChanges() at all at the end of the web request lifecycle yet an update/create/delete is made. Im assuming that EF/Windsor handles the saving for me.
My thoughts is that because this flow is different from all my other endpoints and that its handling two Async calls that somewhere in between I am disposing the DbContext and that is maybe why im seeing it failing on the second (CreateAsync) call. Not sure, just my thought here.
Anyway, sorry for the long winded post here. I wanted to post as much info as possible and am hoping that this may also help someone else facing this or similar issue.
Thanks!
UPDATE 2
I've noticed that after getting this error on /token call, any other (AllowAnonymous) calls i make work - even those that involve the DB. But the /token call in particular no longer works. My only way around this is to restart the server.
UPDATE 3 I was able to reproduce this issu ONLY on mobile testing (linked to Azure server) but cannot reproduce locally. Steps I used to reproduce:
- Log in with one account
- Logout
- Log in with another account
- Logout
- Log in with the first account I tried) - This FAILS
