Search code examples
c#.netentity-framework-6owincastle-windsor

OWIN Store update, insert, or delete statement affected an unexpected number of rows (0) Error


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:

enter image description here

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: enter image description here

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:

  1. Log in with one account
  2. Logout
  3. Log in with another account
  4. Logout
  5. Log in with the first account I tried) - This FAILS

Solution

  • Alright ya'll I was able to figure out this issue and i'll do my best to describe what was happening here.

    For those of you who have followed a tutorial such as this one or any other similar one, you'll see that you basically have some repository structure set up along with maybe your own context which you inherit the base context, right?

    In my case, I was handling the Dispose of the context at the end of the request by overriding the Dispose(bool disposing) method found in the ApiController class. If you're building a WebApi, you'll know what im talking about because any controllers you write inherit this. And if you've got some IoC set up with Lifetimes set to the end of a request, it'll dispose there :)

    However, in this case of the /token call I noticed that we were never hitting any controllers...or at least none that utilized ApiController so i couldn't even hit that Dispose method. That meant that the context stayed "active". And in my second, third, fourth, etc calls to /token endpoint, I noticed in the watcher that the context calls were building up (meaning it was saving prior edits to the context i made from previous /token calls - they were never getting disposed! Thus making the context stale).

    Unfortunately, the way around this for my situation was to wrap any context calls made within the /token request in a using statement, that way i knew after the using finished up it would dispose properly. And this seemed to work :)

    So if you've got a similar project laid out to mine or similar to that tutorial i shared you may run into this issue.