AngularJS - Handling refresh token? - javascript

I'm building a SPA with AngularJS with communication to a service (JAVA).
When user sends his username/pass, service sends back both: Acces token and Refresh token. I'm trying to handle: if I get response with status 401, send back refresh token and then send your last request again. I tried to do that with including $http, but angular doesn't let me include it in this interceptor. Is there any way to recreate the original request with this response parameter I'm recieving?
Something like:
I get 401
save my request
if I have a refresh token send that refresh token
on success resend my request
on error redirect to /login page
'use strict';
angular.module('testApp')
.factory('authentificationFactory', function($rootScope, $q, $window, $location, CONF) {
return {
request: function(config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
}
console.log(config);
$rootScope.lastRequest = config;
return config;
},
response: function(response) {
console.log($rootScope.lastRequest);
if (response.status === 401) {
if ($window.sessionStorage.refreshToken) {
//Save, request new token, send old response
//if it fails, go to login
$location.url('/login');
} else {
$location.url('/login');
}
}
return response || $q.when(response);
}
};
});
Bonus Question (the main question is more important): There are 2 mobile apps that will also connect to my service, and when I log in from my web app, and few moments later from my mobile app, mobile app takes a new refresh token and my web app's refresh token is valid no more. What would be the best option for dealing with that?
Thank you for your time,
Best regards

Have a look at this: https://github.com/witoldsz/angular-http-auth.
He uses a buffer to replay the requests after authentication.

I would strongly advise against sending and storing refresh tokens on SPAs like Angular.
If you are using session storage or local storage, you are opening a window of opportunity for the this refreshToken to be captured, either by a XSS attack, or by the user leaving the computer unattended.
See this article or this question for more info.

Related

Refresh token for Google OAuth 2.0

I have an application that uses Google APIs in some places, and I want to avoid having the user login every time. Currently, I only receive a Google access token in the response, not a refresh token. How can I obtain the refresh token?
This is the function I use to when the user click to login with google:
authenticate() {
const client = google.accounts.oauth2.initTokenClient({
client_id: 'MY_CLIENT_ID',
scope: [
"All_NECESSARY_SCOPES",
].join(" "),
callback: (res) => {
this.accessToken = res.access_token
this.loadClient()
}
})
client.requestAccessToken()
},
It works for getting the access token. And I need the refresh token, Please Help Me :)
Client side JavaScript does not return a refresh token. It would be a security risk.
If you want a refresh token you need to use a server sided language like Node.js

AngularJs: how to hold all the $http request if its thrown unauthorised request error refresh token and resume all the request

i have developed single page application in angularjs. i have implemented the refresh token mechanism. refresh token suppose to refresh every 30 minutes. I am trying to handle refresh token in responseError of interceptor. I m trying to hold request if it returns 401 unauthorised error. Is there any mechanism to hold all the request once it return 401 error then refresh token and resume all request with new token.
Is it right way to handle the refresh token, here is sample code
$provide.factory('httpTokenInterceptor', function ($q, $injector, $cookies) {
return {
// On request sending
request: function (config) {
config.headers = config.headers || {};
// get this data from $cookies
var globals = $cookies.getObject('globals') || {};
//console.log(globals);
if (globals.authData)
config.headers.Authorization = 'Bearer ' + globals.authData.access_token;
return config;
},
// On response failure
responseError: function (rejection) {
console.log('AuthTokenHttpInterceptor responseError');
console.log(rejection);
if (rejection.status === 401) {
//hold current and all pending request
var aService = $injector.get('authenticationService');
aService.getRefreshToken().then(function(response) {
//need to resume all the request here
deferred.resolve(response);
});
return deferred.promise;
}
return $q.reject(rejection);
}
};
});
In short, you don't want to hold up any of your HTTP calls like that.
Your solution will go and refresh your token after one of your HTTP calls already failed. Also, just to be clear, your code is adding Authorization header even on HTTP calls that are getting resources like HTML templates. If you don't want to do this, then you should restrict that as well.
For one solution, check out this link. It doesn't use any particular library for handling JWT tokens, but you will have to create a wrapper around this implementation to use it wherever you need to do a HTTP call.
My suggestion (and personal preference when handling JWT tokens) is using the angular-jwt library. It's really easy to set up and you can check it out here.
There more complex libraries like auth0, which can do a lot of other stuff, and can be used in conjuction with angular-jwt library. Check out this link to see how to handle token refreshing both prior to a HTTP call and on page refresh.
Hope this helps.
You can hold requests and resume them using AngularJS Interceptors.
authInterceptor.$inject = ['$q', '$rootScope'];
function authInterceptor($q, $rootScope) {
return {
request: function(config) {
var deferred = $q.defer();
$rootScope.$watch('continue', function(value) {
if(value === true)
deferred.resolve(config);
});
return deferred.promise;
}
};
}
In the above example all of the requests hold until $rootScope.continue becomes true. Otherwise they will wait forever.

SPA - Firebase and .Net WebApi 2 authentication

I'm having a Single Page Application written in AngularJs (The framework is irrelevant at this point) The application is hosted in IIS and it's compose of index.html plus a bunch of client assets.
On backend I have WebApi 2, hosted also in IIS as a separate application.
For authentication on client I'm using Firebase (simple login) with several social netoworks enabled, like Facebook, Twitter or Google.
So far so good. I like how easy it is to enable twitter authentication for example with firebase.
On login with social network i get back from firebase, the firebaseAuthToken and provider accesstoken.
Now I want to use firebaseAuthToken or provider access token to authenticate with my WebApi.
The question is: What is the best way to authenticate with WebApi in given conditions?
There is not an option to use only firebase to store my data and get rid of web api since I have in place complex business logic on server.
One silly idea that i have so far, is to pass the social provider access token to the server, validate the token against provider and then issue a security token using Owin -Katana.
I'm not using build in social providers support from katana due to lack of documentation, complexity and bad integration with single page apps. I found the visual studio template for SPA too mvc specific. But that's me :)
tl;dr - Demo Project on GitHub
The steps below may seem long, but it's actually really easy. I created my demo project in just an hour or so.
I agree with you about using Owin and Katana. I've been through that process before and it wasn't really a great experience. Using Firebase was a heck of a lot easier.
This can all be done with JWTs!
When you authenticate through Firebase and whatever social provider, you get back a JSON Web Token (JWT) - firebaseAuthToken.
Grab your Firebase Secret from the Dashboard
The way JWTs work is that we have a secret token and a client token. The client token is the firebaseAuthToken we receive after logging in. The secret token is generated for us in the Firebase Dashboard.
Store your Firebase Secret in the appSettings section of your Web.config
We need to store this secret key in the Web.config so it's easier to access later.
<add key="FirebaseSecret" value="<Firebase-Secret-Token-Goes-Here" />
Create an Action Filter to check the JWT from the Authorization Header
We can verify the request is valid by passing the client token in the Authorization header. On the server we can store our secret key that we get from our Firebase Dashboard. When the request is checked by Web API we can decode the JWT using a JWT Library (available from NuGet). If the decoding is successful then we can check to token to make sure it isn't expired.
public class DecodeJWT: ActionFilterAttribute
{
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
string firebaseAuthToken = string.Empty;
if (actionContext.Request.Headers.Authorization != null) {
firebaseAuthToken = actionContext.Request.Headers.Authorization.Scheme;
} else {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
}
string secretKey = WebConfigurationManager.AppSettings["FirebaseSecret"];
try {
string jsonPayload = JWT.JsonWebToken.Decode(firebaseAuthToken, secretKey);
DecodedToken decodedToken = JsonConvert.DeserializeObject < DecodedToken > (jsonPayload);
// TODO: Check expiry of decoded token
} catch (JWT.SignatureVerificationException jwtEx) {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
} catch (Exception ex) {
throw new HttpException((int) HttpStatusCode.Unauthorized, "Unauthorized");
}
base.OnActionExecuting(actionContext);
}
}
Create a $httpInterceptor add the firebaseAuthToken to the header for every request
On the client, the trick is that the token has to be passed every time. To make this easier we need to create a $httpInterceptor with Angular that checks for a firebaseAuthToken on sessionStorage.
.factory('authInterceptor', function ($rootScope, $q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.firebaseAuthToken) {
config.headers.Authorization = $window.sessionStorage.firebaseAuthToken;
}
return config;
},
response: function (response) {
if (response.status === 401) {
// TODO: User is not authed
}
return response || $q.when(response);
}
};
})
Set the firebaseAuthToken to sessionStorage on a successful login
Whenever a user logs in we can set the value to sessionStorage.
$rootScope.$on('$firebaseSimpleLogin:login',
function (e, user) {
// add a cookie for the auth token
if (user) {
$window.sessionStorage.firebaseAuthToken = user.firebaseAuthToken;
}
cb(e, user);
});
Register the DecodeJWT filter globally
Inside of the WebApiConfig.cs Register method we can set the DecodeJWT filter to apply for all of our ApiControllers.
config.Filters.Add(new DecodeJWT());
Now whenever we make a request to an ApiController it will reject it unless there is a valid JWT. So after a user logs in we can save their data to a ApiController if it already doesn't exist.
// globally uses DecodeJWT
public class UsersController: ApiController
{
// POST api/users
public void Post([FromBody] FbUser user) // See GitHub for this Model
{
// Save user if we do not already have it
}
}

Using Angular.js - how to serve binary data from a backend that requires authentication?

In my angularjs application I am communicating with a backend server that requires basic access authentication via http header. I have implemented the authentication mechanism on the client side as described here.
angular.module('myAuthModule')
.config(['$httpProvider', '$stateProvider',
function ($httpProvider, $stateProvider) {
$httpProvider.interceptors.push('securityInterceptor');
}])
.factory('securityInterceptor', ['$location', '$window', '$q',
function ($location, $window, $q) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers['Auth-Key'] = $window.sessionStorage.token;
}
return config;
},
response: function (response) {
if (response.status === 401 || response.status === 403) {
$location.path('/login');
}
return response || $q.when(response);
}
};
}
]);
So far so good, handling xhr requests within the angular app works as expected.
The problem is that I need to provide a download link for pdf documents. My backend server has a /Document/Pdf/:id resource that serves a application/pdf response with ContentDisposition: attachment which also requires authentication. I understand that I cannot initiate a download using xhr, however both providing a link to the document download via ngHref and calling a function that does for example $window.open('/Document/Pdf/13') lead to a 401 Unauthorized response by the server.
What am I missing here?
Having explored the possibilities given by #Geoff Genz with the addition of a fourth - data-uri option, which unfortunately does not allow defining filenames - I decided to go for a different approach.
I added a method to the API which generates a one-time download link based on a normally authenticated request and download it straight away. The angular handler becomes very simple
.factory('fileFactory', ['$http', '$window',
function ($http, $window) {
return {
downloadFile: function (fileId) {
return $http(
{
method: "POST",
data: fileId,
url: '/api/Files/RequestDownloadLink',
cache: false
}).success(function (response) {
var url = '/api/File/' + response.downloadId;
$window.location = url;
});
}
};
}]);
This is not perfect but I feel is least hack-ish. Also this works for me because I have full control of the front- and back-end.
There is not a simple solution to this. You've already discovered that you cannot download via Ajax, so you can't set a custom header that way. Nor can you set a custom header on a browser generated GET (like an href) or POST (like a form submit). I can suggest three different approaches, all of which will require some modifications on your server:
(1) Use Basic or Digest auth on your web page, so the browser will generate and send the Authorization header with those credentials.
(2) Set the token in "authorization" cookie that will be passed with the request and validate the token server side.
(3) Finally, the way we've implemented this is to use a POST request instead of a GET for the download. We POST to a hidden IFrame on the same page and have the server set the appropriate Content-Disposition header such as "attachment; filename="blah.pdf"" on the response. We then send the authorization token as a hidden field in the form.
None of these are ideal, and I know our solution feels kind of hacky, but I've not seen any more elegant approaches.

AngularJS Authentication with a restful api that returns a token on success

I'm working with a restful API that when authenticating a user successfully, the request returns a token. The token is then added as a header on every request like this:
Authorization: Bearer <token>
I struggled to find a good way to do authentication without a lot of code bloat.
I came up with a good solution using HTML5 sessionStorage. Here's a simple example:
// Main module declaration
var myapp = angular.module('myapp', []);
// Set some actions to be performed when running the app
myapp.run(['$location', '$rootScope',
function($location, $rootScope) {
// Register listener to watch route changes.We use this to make
// sure a user is logged in when they try to retrieve data
$rootScope.$on("$routeChangeStart", function(event, next, current) {
// If there is no token, that means the user is not logged in
if (sessionStorage.getItem("token") === null) {
// Redirect to login page
window.location.href = "login.html";
}
});
}]);
// A controller for the login page
myapp.controller('LoginController', ['$scope', '$http',
function($scope, $http) {
// If a user has check the "remember me" box previously and the email/pass
// is in localStorage, set the email/password
// Login method when the form is submitted
$scope.login = function() {
// Authenticate the user - send a restful post request to the server
// and if the user is authenticated successfully, a token is returned
$http.post('http://example.com/login', $scope.user)
.success(function(response) {
// Set a sessionStorage item we're calling "token"
sessionStorage.setItem("token", response.token);
// Redirect to wherever you want to
window.location = 'index.html';
});
};
}]);

Categories