How to create Refresh Token with External Login Provider?

前端 未结 3 793
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-08 11:16

I have searched over the web and could not find a solution to my problem. I am implementing OAuth in my app. I am using ASP .NET Web API 2, and Owin. The scenario is this, o

相关标签:
3条回答
  • 2020-12-08 11:26

    Finally found the solution for my problem. First of all, if you EVER encounter any problems with OWIN and you cannot figure out what is going wrong, I advise you to simply enable symbol-debugging and debug it. A great explanation can be found here: http://www.symbolsource.org/Public/Home/VisualStudio

    My mistake simply was, that I was calculating a wrong ExiresUtc when using external login providers. So my refreshtoken basically was always expired right away....

    If you are implementing refresh tokens, then look at this gread blog article: http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

    And to make it work with refresh tokens for external providers, you have to set the two requried parameters ("as:clientAllowedOrigin" and "as:clientRefreshTokenLifeTime") on the context so instead of

    
     var ticket = new AuthenticationTicket(oAuthIdentity, properties);
    var context = new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
                        Request.GetOwinContext(), 
                        Startup.OAuthOptions.AccessTokenFormat, ticket);
    
     await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
     properties.Dictionary.Add("refresh_token", context.Token);
    
    

    you need to get the client first and set the context parameters

    
        // retrieve client from database
        var client = authRepository.FindClient(client_id);
        // only generate refresh token if client is registered
        if (client != null)
        {
            var ticket = new AuthenticationTicket(oAuthIdentity, properties);
            var context = new AuthenticationTokenCreateContext(Request.GetOwinContext(), AuthConfig.OAuthOptions.RefreshTokenFormat, ticket);
            // Set this two context parameters or it won't work!!
            context.OwinContext.Set("as:clientAllowedOrigin", client.AllowedOrigin);
            context.OwinContext.Set("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
    
            await AuthConfig.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
            properties.Dictionary.Add("refresh_token", context.Token);
        }
    
    
    0 讨论(0)
  • 2020-12-08 11:27

    I spent a lot of time to find the answer to this question. So, i'm happy to help you.

    1) Change your ExternalLogin method. It usually looks like:

    if (hasRegistered)
    {
         Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    
         ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
                    OAuthDefaults.AuthenticationType);
         ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
                    CookieAuthenticationDefaults.AuthenticationType);
    
         AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
         Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
    }
    

    Now, actually, it is necessary to add refresh_token. Method will look like this:

    if (hasRegistered)
    {
         Authentication.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    
         ClaimsIdentity oAuthIdentity = await user.GenerateUserIdentityAsync(UserManager,
                       OAuthDefaults.AuthenticationType);
         ClaimsIdentity cookieIdentity = await user.GenerateUserIdentityAsync(UserManager,
                        CookieAuthenticationDefaults.AuthenticationType);
    
         AuthenticationProperties properties = ApplicationOAuthProvider.CreateProperties(user.UserName);
    
         // ADD THIS PART
         var ticket = new AuthenticationTicket(oAuthIdentity, properties);
         var accessToken = Startup.OAuthOptions.AccessTokenFormat.Protect(ticket);
    
                    Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context = 
                        new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
                            Request.GetOwinContext(), 
                            Startup.OAuthOptions.AccessTokenFormat, ticket);
    
         await Startup.OAuthOptions.RefreshTokenProvider.CreateAsync(context);
         properties.Dictionary.Add("refresh_token", context.Token);
    
         Authentication.SignIn(properties, oAuthIdentity, cookieIdentity);
    }
    

    Now the refrehs token will be generated.

    2) There is a problem to use basic context.SerializeTicket in SimpleRefreshTokenProvider CreateAsync method. Message from Bit Of Technology

    Seems in the ReceiveAsync method, the context.DeserializeTicket is not returning an Authentication Ticket at all in the external login case. When I look at the context.Ticket property after that call it’s null. Comparing that to the local login flow, the DeserializeTicket method sets the context.Ticket property to an AuthenticationTicket. So the mystery now is how come the DeserializeTicket behaves differently in the two flows. The protected ticket string in the database is created in the same CreateAsync method, differing only in that I call that method manually in the GenerateLocalAccessTokenResponse, vs. the Owin middlware calling it normally… And neither SerializeTicket or DeserializeTicket throw an error…

    So, you need to use Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer to searizize and deserialize ticket. It will be look like this:

    Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer
                    = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
    
    token.ProtectedTicket = System.Text.Encoding.Default.GetString(serializer.Serialize(context.Ticket));
    

    instead of:

    token.ProtectedTicket = context.SerializeTicket();
    

    And for ReceiveAsync method:

    Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer serializer = new Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer();
    context.SetTicket(serializer.Deserialize(System.Text.Encoding.Default.GetBytes(refreshToken.ProtectedTicket)));
    

    instead of:

    context.DeserializeTicket(refreshToken.ProtectedTicket);
    

    3) Now you need to add refresh_token to ExternalLogin method response. Override AuthorizationEndpointResponse in your OAuthAuthorizationServerProvider. Something like this:

    public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context)
    {
         var refreshToken = context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["refresh_token"];
         if (!string.IsNullOrEmpty(refreshToken))
         {
              context.AdditionalResponseParameters.Add("refresh_token", refreshToken);
         }
         return base.AuthorizationEndpointResponse(context);
    }
    

    So.. thats all! Now, after calling ExternalLogin method, you get url: https://localhost:44301/Account/ExternalLoginCallback?access_token=ACCESS_TOKEN&token_type=bearer&expires_in=300&state=STATE&refresh_token=TICKET&returnUrl=URL

    I hope this helps)

    0 讨论(0)
  • 2020-12-08 11:36

    @giraffe and others offcourse

    A few remarks. There's no need to use the custom tickerserializer.

    The following line:

    Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext context = 
                    new Microsoft.Owin.Security.Infrastructure.AuthenticationTokenCreateContext(
                        Request.GetOwinContext(), 
                        Startup.OAuthOptions.AccessTokenFormat, ticket);
    

    As tokenformat: Startup.OAuthOptions.AccessTokenFormat is used. Since we want to provide a refeshtoken this needs te be changed to: Startup.OAuthOptions.RefreshTokenFormat

    Otherwise if you want to get a new accesstoken and refresh the refreshtoken ( grant_type=refresh_token&refresh_token=...... ) the deserializer/unprotector will fail. Since it uses the wrong purposes keywords at the decrypt stage.

    0 讨论(0)
提交回复
热议问题