My angular application is making use of bearer tokens as outlined in the article series http://bitoftech.net/2014/06/01/token-based-authentication-asp-net-web-api-2-owin-asp-net-identity/. I have followed the forked example to seamlessly refresh tokens when the access token has expired (via 401 http code).
My question is how can I determine if the bearer token is expired or just plain unauthorized based on the role determined?
For example, my web api method has the attribute [Authorize(Roles="Admin")]. When I make a call to that, I get back my 401 error, which is expected. However, when my access token expires and I make another web api method call, it also returns a 401 error. Heres my responseError handler in my interceptor:
responseError: function (rejection) {
var deferred = q.defer();
if (rejection.status === 401) {
var authService = $injector.get('authService');
authService.refreshToken().then(function (response) {
_retryHttpRequest(rejection.config, deferred);
}, function () {
authService.logOut();
$location.path('/dashboard');
deferred.reject(rejection);
});
} else {
deferred.reject(rejection);
}
return deferred.promise;
}
I was playing around with different things but basically, I'd like to refresh my token and resend my request when the access token has expired; however, I don't want to refresh my token if it truly is a denied request due to the role specified.
Any thoughts?
As noted in my response to Cory Silva's comment, the Web API Authorize attribute will always return 401 unauthorized for both authentication AND authorization.
See article and thread below:
http://leastprivilege.com/2014/10/02/401-vs-403/
It looks like there are two options:
When I store the token retrieved from my authorization server in localStorage, I also store the token's expiration. In the interceptor responseError function, I compare the stored token expiration with the current datetime. If it's determined to be expired, refresh the token and resend the request.
responseError: function (rejection) {
var deferred = q.defer();
if (rejection.status === 401) {
var tokenExpired = false;
var authData = localStorage.get('authorizationData');
if (authData) {
tokenExpired = moment().isAfter(authData.expiration);
}
if (tokenExpired) {
var authService = auth;//$injector.get('authService');
authService.refreshToken().then(function (response) {
_retryHttpRequest(rejection.config, deferred);
}, function () {
authService.logOut();
$state.go('error');
deferred.reject(rejection);
});
}
else {
$state.go('error');
deferred.reject(rejection);
}
} else {
$state.go('error');
deferred.reject(rejection);
}
return deferred.promise;
}
Use the accepted answer in the stackoverflow thread I referenced above and create my own AuthorizeAttribute to determine token expiration vs. unauthorized access.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden);
}
else
{
base.HandleUnauthorizedRequest(actionContext);
}
}
}
I think I'm going to use option 2 so that the error codes a little clearer to the client.