AngularJS clientside routing and token authentication with webapi
I want to create an example for authentication and authorization in an SPA angularjs application using asp.net mvc webapi as the backend and client side routing (no cshtml). Below is just example of functions that can be used to set up the complete example. But I just can´t put it all togehter. Any help appreciated.
Questions:
- What is best practise: Cookie or Token based?
- How do I create the bearer token in angular to authorize on each request?
- Validation on API functions?
- How do I preserve the autentication signed in user on the client?
Example code:
-
Sign in form
<form name="form" novalidate> <input type="text" ng-model="user.userName" /> <input type="password" ng-model="user.password" /> <input type="submit" value="Sign In" data-ng-click="signin(user)"> </form>
-
Authentication Angular Controller
$scope.signin = function (user) { $http.post(uri + 'account/signin', user) .success(function (data, status, headers, config) { user.authenticated = true; $rootScope.user = user; $location.path('/'); }) .error(function (data, status, headers, config) { alert(JSON.stringify(data)); user.authenticated = false; $rootScope.user = {}; }); };
-
My API backend API Code.
[HttpPost] public HttpResponseMessage SignIn(UserDataModel user) { //FormsAuthetication is just an example. Can I use OWIN Context to create a session and cookies or should I just use tokens for authentication on each request? How do I preserve the autentication signed in user on the client? if (this.ModelState.IsValid) { if (true) //perform authentication against db etc. { var response = this.Request.CreateResponse(HttpStatusCode.Created, true); FormsAuthentication.SetAuthCookie(user.UserName, false); return response; } return this.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Invalid username or password"); } return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState); }
-
Authorization Using the JWT library for restricting content.
config.MessageHandlers.Add(new JsonWebTokenValidationHandler { Audience = "123", SymmetricKey = "456" });
-
My API methods
[Authorize] public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; }
Solution 1:
Whether to use cookie authentication or (bearer) tokens still depends on the type of app you have. And as far as I know there aren't any best practice yet. But since you are working on a SPA, and are already using a JWT library, I would favor the token based approach.
Unfortunately, I cannot help you with ASP.NET, but usually JWT libraries generate and verify the token for you. All you have to do is call generate
or encode
on the credentials (and the secret) and verify
or decode
on the token sent with every request. And you don't need to store any state on the server and don't need to send a cookie, what you probably did with FormsAuthentication.SetAuthCookie(user.UserName, false)
.
I'm sure your library provides an example on how to use generate/encode and verify/decode tokens.
So generating and verifying is not something you do on the client side.
The flow goes something like this:
- Client sends the user provided login credentials to the server.
- Server authenticates credentials and responds with a generated token.
- Client stores the token somewhere (local storage, cookies, or just in memory).
- Client sends the token as an authorization header on every request to the server.
- Server verifies the token and acts accordingly with either sending the requested resource, or an 401 (or something alike).
Step 1 and 3:
app.controller('UserController', function ($http, $window, $location) {
$scope.signin = function(user) {
$http.post(uri + 'account/signin', user)
.success(function (data) {
// Stores the token until the user closes the browser window.
$window.sessionStorage.setItem('token', data.token);
$location.path('/');
})
.error(function () {
$window.sessionStorage.removeItem('token');
// TODO: Show something like "Username or password invalid."
});
};
});
sessionStorage
keeps the data as long as the user has the page open. In case you want to handle expiration times yourself, you could use localStorage
instead. The interface is the same.
Step 4:
To send the token on every request to the server, you can use what Angular calls an Interceptor. All you have to do is get the previously stored token (if any) and attach it as a header to all outgoing requests:
app.factory('AuthInterceptor', function ($window, $q) {
return {
request: function(config) {
config.headers = config.headers || {};
if ($window.sessionStorage.getItem('token')) {
config.headers.Authorization = 'Bearer ' + $window.sessionStorage.getItem('token');
}
return config || $q.when(config);
},
response: function(response) {
if (response.status === 401) {
// TODO: Redirect user to login page.
}
return response || $q.when(response);
}
};
});
// Register the previously created AuthInterceptor.
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('AuthInterceptor');
});
And make sure to always use SSL!