OAuth access and refresh token control / management on user password change

删除回忆录丶 提交于 2020-01-01 19:41:13

问题


We are in the process of developing a in house mobile application and web api. We are using asp.net web api 2 with asp.net Identy 2 OAuth.

I have got the api up and running and giving me a bearer token. However I want to slightly modify the process flow to something like along the lines of this:

  1. App user logs in to api with username and password.
  2. App receives Refresh-token which is valid for 30 days.
  3. App then requests an access token providing the api with the refresh token. ( Here I want to be able to invalidate a request if the user has changed their password or their account has been locked).
  4. App Gets An Access token which is valid for 30 minutes or it gets a 401 if the password check failed.
  5. The App can access the api with the given access token for the next 29 minutes. After that the app will have to get a new access token with the refresh token.

Reason I want to do this is in order to stop a users devices gaining access to the api after they have changed their password. If their phone gets stolen they need to be able to login to the website and change their password so the new owner of the phone cannot gain access to our companies services.

Is my proposed solution do able, and if so is it a sensible solution? I haven't forgotten any crucial elements?

I am willing to do a db access on ever token refresh, but not on every API call.

To summarize my questions are:

  1. Is my planned method sensible?
  2. How would I securely check if the password has changed or if account is locked in the refresh token process.

Please find my current OAuth Setups and classes below: (I have tried to add the refresh token functionality, but have not attempted to add any password verification yet)

Startup.Auth.cs

public partial class Startup
{
    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    public static string PublicClientId { get; private set; }

    // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864
    public void ConfigureAuth(IAppBuilder app)
    {
        // Configure the db context and user manager to use a single instance per request
        app.CreatePerOwinContext(IdentityDbContext.Create);
        app.CreatePerOwinContext<FskUserManager>(FskUserManager.Create);

        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

        // Configure the application for OAuth based flow
        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            RefreshTokenProvider = new ApplicationRefreshTokenProvider(),
            AllowInsecureHttp = true
        };

        // Enable the application to use bearer tokens to authenticate users
        app.UseOAuthBearerTokens(OAuthOptions);
}

ApplicationRefreshTokenProvider.cs

public class ApplicationRefreshTokenProvider : AuthenticationTokenProvider
        {
            public override void Create(AuthenticationTokenCreateContext context)
            {
                // Expiration time in minutes
                int refreshTokenExpiration = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["ApiRefreshTokenExpiry"]);
                context.Ticket.Properties.ExpiresUtc = new DateTimeOffset(DateTime.Now.AddMinutes(refreshTokenExpiration));
                context.SetToken(context.SerializeTicket());
            }

            public override void Receive(AuthenticationTokenReceiveContext context)
            {
                context.DeserializeTicket(context.Token);
            }
        }   

ApplicationOAuthProvider.cs

public class ApplicationOAuthProvider : OAuthAuthorizationServerProvider
{
    private readonly string _publicClientId;

    public ApplicationOAuthProvider(string publicClientId)
    {
        if (publicClientId == null)
        {
            throw new ArgumentNullException("publicClientId");
        }

        _publicClientId = publicClientId;
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var userManager = context.OwinContext.GetUserManager<FskUserManager>();

        FskUser user = await userManager.FindAsync(context.UserName, context.Password);

        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(userManager,
           OAuthDefaults.AuthenticationType);
        ClaimsIdentity cookiesIdentity = await user.GenerateUserIdentityAsync(userManager,
            CookieAuthenticationDefaults.AuthenticationType);

        AuthenticationProperties properties = CreateProperties(user.UserName);
        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        context.Validated(ticket);
        context.Request.Context.Authentication.SignIn(cookiesIdentity);
    }

    public override Task TokenEndpoint(OAuthTokenEndpointContext context)
    {
        foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
        {
            context.AdditionalResponseParameters.Add(property.Key, property.Value);
        }

        return Task.FromResult<object>(null);
    }

    public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        // Resource owner password credentials does not provide a client ID.
        if (context.ClientId == null)
        {
            context.Validated();
        }

        return Task.FromResult<object>(null);
    }

    public override Task ValidateClientRedirectUri(OAuthValidateClientRedirectUriContext context)
    {
        if (context.ClientId == _publicClientId)
        {
            Uri expectedRootUri = new Uri(context.Request.Uri, "/");

            if (expectedRootUri.AbsoluteUri == context.RedirectUri)
            {
                context.Validated();
            }
        }

        return Task.FromResult<object>(null);
    }

    public static AuthenticationProperties CreateProperties(string userName)
    {
        IDictionary<string, string> data = new Dictionary<string, string>
        {
            { "userName", userName }
        };
        return new AuthenticationProperties(data);
    }
}

回答1:


If I understood your task right, here is an idea.

On the create access token event, you can check if the password has been changed from the website and if so, revoke the refresh token. (you can create some flag that the password has been changed or something)

It should not be often when you are creating an access token so there should be no problems with the db access.

Now the question is how to revoke a refresh token. Unless there is a build in way you will have to implement a custom one. An idea here is to check the refresh token creation date and the date of the change password operation. If the change password operation is done after the creation of the refresh token, you do not authenticate the user.

Let me know what you think of this.



来源:https://stackoverflow.com/questions/29534111/oauth-access-and-refresh-token-control-management-on-user-password-change

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!