在 JavaScript 中存储刷新令牌,获取新的访问令牌
Storing Refresh Token In JavaScript, Getting New Access Token
我有一个 ASP.NET Web API,登录时 returns 有一个 OAuth2 不记名令牌。我计划通过 JavaScript 将刷新令牌存储在 cookie 中。在我的 JS 中,我应该检查访问令牌是否已过期以获取新的访问令牌......就在每个需要身份验证的后续 API 调用之前或某种计时器循环之前?这将是一个异步网络应用程序,所以我认为计时器循环不是理想的选择。有什么想法吗?
我不会那样做,如果你可以在 javascript 中访问你的 cookie,这意味着任何 XSS 脚本都可以劫持它。 Localstorage 也不是一个安全的地方。
我建议在服务器上生成令牌后立即将令牌存储在安全 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 中解析令牌并将令牌设置为授权 Header.
要刷新令牌,您可以配置一个 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
希望对您有所帮助。
我有一个 ASP.NET Web API,登录时 returns 有一个 OAuth2 不记名令牌。我计划通过 JavaScript 将刷新令牌存储在 cookie 中。在我的 JS 中,我应该检查访问令牌是否已过期以获取新的访问令牌......就在每个需要身份验证的后续 API 调用之前或某种计时器循环之前?这将是一个异步网络应用程序,所以我认为计时器循环不是理想的选择。有什么想法吗?
我不会那样做,如果你可以在 javascript 中访问你的 cookie,这意味着任何 XSS 脚本都可以劫持它。 Localstorage 也不是一个安全的地方。
我建议在服务器上生成令牌后立即将令牌存储在安全 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 中解析令牌并将令牌设置为授权 Header.
要刷新令牌,您可以配置一个 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
希望对您有所帮助。