在 JavaScript 中存储刷新令牌,获取新的访问令牌

Posted

技术标签:

【中文标题】在 JavaScript 中存储刷新令牌,获取新的访问令牌【英文标题】:Storing Refresh Token In JavaScript, Getting New Access Token 【发布时间】:2015-09-17 00:11:33 【问题描述】:

我有一个 ASP.NET Web API,它在登录时返回一个 OAuth2 不记名令牌。我计划通过 javascript 将刷新令牌存储在 cookie 中。我应该在我的 JS 中的哪个位置检查访问令牌是否已过期以获取新的...在每个需要身份验证的后续 API 调用之前或在某种计时器循环上?这将是一个异步 Web 应用程序,所以我认为计时器循环并不理想。有什么想法吗?

【问题讨论】:

【参考方案1】:

我不会那样做,如果您可以在 javascript 中访问您的 cookie,这意味着任何 XSS 脚本都可以劫持它。本地存储也不是一个安全的地方。

我建议在服务器上生成令牌后立即将令牌存储在安全的 http-only cookie 中,并将该 cookie 附加到响应中。

如果您将 WebAPI 2 与 oAuth 一起使用,则在您的授权服务器提供程序中,您可以覆盖 TokenEndPointReponse

    /// <summary>
    /// Called when a request to the Token endpoint is finished and gonna be sent back to the client
    /// We intercept the token and force the client to write a cookie containing the token value.
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public override System.Threading.Tasks.Task TokenEndpointResponse(OAuthTokenEndpointResponseContext context)
    

        // We set our auth cookie configuration
        var cookieOptions = new CookieOptions
        
            HttpOnly = true, // immune to JS manipulation
            Secure = insertMagicLogicHere(), // we set the cookie to secure in production environment
            Path = "/",
            Domain = ".mycooldomain.com",
            Expires = DateTime.Now.AddMinutes(GlobalAuthSettings.AuthServerOptions.AccessTokenExpireTimeSpan.TotalMinutes)
        ;

        var refreshTokenId = context.OwinContext.Get<string>("as:refreshTokenId");

        // We build the authentication object to store in our cookie
        var ourTokenObject = new AuthCookie
        
            username = context.Properties.Dictionary["userName"],
            token = context.AccessToken,
            useRefreshTokens = true,
            refreshtoken = refreshTokenId
        ;

        // We send it back 
        context.Response.Cookies.Append("mySecureCookie", JsonConvert.SerializeObject(ourTokenObject), cookieOptions);

        return base.TokenEndpointResponse(context);
    

然后在 javascript 中,您需要确保 cookie 随每个请求一起发送。 您可以设置一些 Owin 中间件来拦截请求,从 cookie 中解析令牌并将令牌设置为授权标头。

要刷新令牌,您可以配置一个 Http 拦截器,如果您收到 401 将自动刷新令牌,如果刷新令牌成功则重试请求。

这是 Angular 中的一些代码

    //#region Private members

    var _retryHttpRequest = function (config, deferred) 
        $rootScope.httpRetries++;
        console.log('autorefresh');
        $http = $http || $injector.get('$http') || $injector.post('$http');
        $http(config).then(
        function (success) 
            $rootScope.httpRetries--;
            deferred.resolve(success);
        ,
        function (error) 
            console.log("Error in response", rejection);
            deferred.reject(error);
        );
    ;

    var _refreshToken = function () 
        var deferred = $q.defer();
        // refresh_token=refreshToken because the refresh_token is serialized in the http cookie and not accessible in javascript, the refreshToken is however stored in the cookie so we can resolve that server side
        var data = "grant_type=refresh_token&refresh_token=refreshToken&client_id=" + appState.clientId;
        $http = $http || $injector.get('$http');
        $http.post(authUrl + '/token', data, 
            headers:  'Content-Type': 'application/x-www-form-urlencoded' 
        ).success(function (response) 
            deferred.resolve(response);
        ).error(function (err, status) 
            deferred.reject(err);
        );
        return deferred.promise;
    ;

    //#endregion Private members

    //#region Public members

    function _request(config) 
        config.withCredentials = true; // forces angular to send cookies in each request
        return config;
    

    // intercept response errors and refresh token if need be
    function _responseError(rejection) 
        var deferred = $q.defer();
        if (rejection.status === 401 || rejection.status === 403) 
            if ($rootScope.httpRetries < 2) 
                console.log("calling refreshToken()");
                _refreshToken().then(function (response) 
                    console.log("token refreshed, retrying to connect");
                    // retry the request if the token was successfully refreshed
                    _retryHttpRequest(rejection.config, deferred);
                , function (error) 
                    console.log("_refreshToken error", error);
                    deferred.reject(rejection);
                    window.location.reload(true);
                );
            
            else 
                console.log("_refreshToken error", error);
                deferred.reject(rejection);
                window.location.reload(true);
            

         else 
            console.log("Error in response", rejection);
            deferred.reject(rejection);
        
        return deferred.promise;
    ;
    //#endregion Public members

希望这会有所帮助。

【讨论】:

以上是关于在 JavaScript 中存储刷新令牌,获取新的访问令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何从 passportjs 中的刷新令牌中获取新的 Google oauth 访问令牌

为啥使用 JWT 刷新令牌

如何使用刷新令牌在 django-oauth-toolkit 上获取新的访问令牌?

如何使用刷新令牌在 django-oauth-toolkit 上获取新的访问令牌?

(JAVA) Google API 分析 - 无法使用“刷新令牌”获取新的“访问令牌”

如何实现刷新令牌轮换?