Introduction
In my previous article, we saw an overview of Token based authentication using ASP.NET web API and OWIN with the AngularJS application. In that post, we had created SPA (single page application) using AngularJS and authentication is done by using OWIN. This flow returns the authorization token and token will expire after certain time, after that user needs to login again. In this post, I will explain how can we alive the token long time using refresh token flow? With the refresh token the user does not need to login again and they use refresh token to request a new authorization token.
Here, idea of using refresh token is to issue short lived access token (around 20-30 minute) at the first time and then use refresh token to obtain new access token. In this idea user need to authenticate himself by providing user name and password and if the information provided by the client is valid, the response contains the short lived access token along with long lived refresh token. The refresh token is not access token but it is just identifier to the refresh token. Now after certain fixed time period, we can use this refresh token identifier and try to obtain another short lived access token. This new access token will use the further communication with server (Web API).
Using the refresh token to a Web API has several advantages:
- The client does not required to hold the user name and password after the token has been generated i.e. the users do not required to re-enter their credentials for the lifetime of the token.
- The back-end need not to validate password on every request.
- The Credential validation is done at the authorization server.
Why are we not issuing long lived access token at the first place instead of adding this complexity
As we know, access tokens are self-descriptive; they contain all claims identity (user information) about the authenticated user once they are issued. Once the user obtaining the long lived token, s/he will be able to access the server resources as long as her/his token is not expire. There is no way to revoke access token unless we implement any custom logic which enforce us to store issued tokens in to the cache or database check with each request. With refresh tokens, a system can be revoked the access token by deleting the token from the cache or database and now Authorization Server will reject the request because the refresh token is no longer available. Refresh token allows us to ask the user name and password to user once for the authentication first time and then Authorization server can issue long lived refresh token and user will stay logged in all this period unless refresh token is not revoked.
Refresh token
Refresh tokens must bound with the client. Client is identity and with help of the client, application is attempting to communicate with the server (back-end API). Every client should have the client id and secret and usually Client Id/Secret is hard coded and it validates at the time of login.
Normally client id is unique public identifiers of our application when other application also use same web API. So this means that we need to authenticate our client first.
My post "Token Based Authentication Using ASP.Net Web API, OWIN and Identity " (step -5) explained, how we can configure the OAuth Authorization Server. ValidateClientAuthentication method is used to validate client credentials and make the client ID available in the pipeline for later processing. GrantResourceOwnerCredentials method is a method where we actual validate the user credential and generate the token.
Here we need to add one more method to the authorization server provider called "GrantRefreshToken". This method is called when refresh token request comes in. In this method, first we need to validate the client and we have chance to modify the outgoing access token and user claims.
- public override Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
- {
-
-
-
-
-
-
-
-
-
-
- var newIdentity = new ClaimsIdentity(context.Ticket.Identity);
- newIdentity.AddClaim(new Claim("newClaim", "newValue"));
-
- var newTicket = new AuthenticationTicket(newIdentity, context.Ticket.Properties);
- context.Validated(newTicket);
-
- return Task.FromResult<object>(null);
- }
To refresh the token, first we need to add new class called "
RefreshTokenProvider" which implements the interface “
IAuthenticationTokenProvider”.
Here we need to create a secure handle to the refresh token and need to store data which associate the authentication ticket. In this article I have used Concurrent Dictionary to store generated refresh token and authentication ticket data.
So, Refresh Token class definition becomes as the following:
- using Microsoft.Owin.Security;
- using Microsoft.Owin.Security.Infrastructure;
- using System;
- using System.Collections.Concurrent;
- using System.Threading.Tasks;
-
- namespace WebAPI
- {
- public class RefreshTokenProvider : IAuthenticationTokenProvider
- {
- private static ConcurrentDictionary<string, AuthenticationTicket> _refreshTokens = new ConcurrentDictionary<string, AuthenticationTicket>();
- public async Task CreateAsync(AuthenticationTokenCreateContext context)
- {
- var guid = Guid.NewGuid().ToString();
-
-
- var refreshTokenProperties = new AuthenticationProperties(context.Ticket.Properties.Dictionary)
- {
- IssuedUtc = context.Ticket.Properties.IssuedUtc,
- ExpiresUtc = DateTime.UtcNow.AddMinutes(60)
- };
- var refreshTokenTicket = new AuthenticationTicket(context.Ticket.Identity, refreshTokenProperties);
-
- _refreshTokens.TryAdd(guid, refreshTokenTicket);
-
-
- context.SetToken(guid);
- }
-
- public void Create(AuthenticationTokenCreateContext context)
- {
- throw new NotImplementedException();
- }
-
- public void Receive(AuthenticationTokenReceiveContext context)
- {
- throw new NotImplementedException();
- }
-
- public async Task ReceiveAsync(AuthenticationTokenReceiveContext context)
- {
- AuthenticationTicket ticket;
- string header = context.OwinContext.Request.Headers["Authorization"];
-
- if (_refreshTokens.TryRemove(context.Token, out ticket))
- {
- context.SetTicket(ticket);
- }
- }
- }
- }
We need to add our refresh token generation logic inside the "CreateAsync" method. The following are some important implementation point of this method.
- Here, we are using a unique identifier for the refresh token. Guid is enough for generating the refresh token key. We can use any other strong algorithm to generate refresh token key.
- After that we are generating refresh token properties by reading context ticket properties and set it’s expiry time.
- After adding all context properties, we are generating refresh token authentication ticket and adding it to the Concurrent Dictionary.
- Finally, call the context.SetToken method.
Now we need to register the token provider in the starting.
- OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
- {
-
- AllowInsecureHttp = true,
- TokenEndpointPath = new PathString("/token"),
- AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(30),
- Provider = new AuthorizationServerProvider(),
- RefreshTokenProvider = new RefreshTokenProvider()
- };
Refresh Token example with AngularJS
In this post, I have used same example which was used in my previous post. Once above described code is completed, we will obtain refresh token along with the access token. Now, this generated refresh token need to be stored somewhere in client side memory, either the client window session or local storage.
In this example, we obtain the refresh token and store it in user info object on windows session. So some changes are required in login method and the definition of login method becomes the following:
“
Login” method in loginservice.js file:
- this.login = function (userName, password) {
- deferred = $q.defer();
- var data = "grant_type=password&username=" + userName + "&password=" + password;
- $http.post(loginServiceURL, data, {
- headers:
- { 'Content-Type': 'application/x-www-form-urlencoded' }
- }).success(function (response) {
- var o = response;
- userInfo = {
- accessToken: response.access_token,
- userName: response.userName,
- refreshToken: response.refresh_token
- };
- authenticationService.setTokenInfo(userInfo);
- authData.authenticationData.IsAuthenticated = true;
- authData.authenticationData.userName = response.userName;
- deferred.resolve(null);
- })
- .error(function (err, status) {
- authData.authenticationData.IsAuthenticated = false;
- authData.authenticationData.userName = "";
- deferred.resolve(err);
- });
- return deferred.promise;
- }
When we made refresh token request, we need to set "grant_type" to "refresh_token" and must pass refresh token key and client id if any with the request. If all went successfully, we will receive new access token and new refresh token key.
To check and validate refresh token code I have made the following changes in my code.
- Add one link called “Refresh Token” in Index.html.
- <li data-ng-hide="!authentication.IsAuthenticated"><a href="" data-ng-click="refreshToken()">Refresh Token</a></li>
- Add the following function to AuthenticationService.js. This method is used to refresh the token.
- this.refreshToken = function() {
- var loginServiceURL = serviceBase + 'token';
- var deferred = $q.defer();
- var token = this.getTokenInfo();
- var data = "grant_type=refresh_token&refresh_token=" + token.refreshToken + "&client_id=";
-
- $http.post(loginServiceURL, data).success(function (response) {
- var o = response;
- var userInfo = {
- accessToken: response.access_token,
- userName: response.userName,
- refreshToken: response.refresh_token
- };
- AuthenticationService.setTokenInfo(userInfo);
- AuthenticationService.setHeader($http);
- hasHttpRequest = false;
- deferred.resolve(null);
- }).error(function (err, status) {
- deferred.resolve(err);
- });
- return deferred.promise;
- }
- Add refresh token method to indexController.js.
- (function () {
-
- 'use strict';
- app.controller('indexController', ['$scope', '$location', 'authData', 'LoginService', 'AuthenticationService', function ($scope, $location, authData, loginService, authenticationService) {
-
- $scope.logOut = function () {
- loginService.logOut();
- $location.path('/home');
- }
- $scope.authentication = authData.authenticationData;
-
- $scope.refreshToken = function () {
- authenticationService.refreshToken();
- }
-
-
- }]);
- })();
Output:
Summary
Hopefully this post is useful for implementing refresh token. Here I have called refresh token method manually (by clicking the link button) but it should be automated i.e. it should be called after specific time period just before expire access token.