4

I am creating 1 website using Web api and angular js and i am so much confused about Authentication to be used in my web site.

I have created one login.js in which there would be my Login method which will post my Username/Emailid and password to my Web Api and the method in web api will authenticate that user.

Code:

$scope.Login()
{
  $.post("api/Authentication/Login",Username,Password)
  {
  }
}

Web api code:

[Route]
Public Task<object> Login([FromBody] username,password)
{
   //method to authenticate user from database.
   //Now the tricky parts comes like after authenticating user should i 
   //just store the user id in my session like this Session["userid]=id or
   //Should i go for storing whole user object in my cookie as i have read 
   //that storing data in session is very bad idea and disaster too as session is very memory
   //and so i think i would go for cookie which will be saved on client side.
   //but what if cookie isnt supported by browser??
}

Using session is disaster as pointed out by Darin Dimitrov in his answer and comments. So i have decided to use cookie as per this answer and one of the ecommerce site that is Nop Commerce uses cookie too to store currently login customer object as per this question and answer Question

I am following this code suggested by LukeP in this Question for authentication purpose and maintaining currenlty login user object across my whole appilcation.

I have read about asp.net claim identity too but dont know whether i can use it in my asp.net web api and angular js.

So can anybody tell me whats the correct approach to use for authentication in asp.net web api and angular js and what all the changes to be done in LukeP code to work with web api and angular js??

Can anybody explain me about this appraoch which i have pointed above with some detail description and some codes too as it can help me and some others too if they are searching for the same.

Later i will offer 100 bounty to the answer addressing all above concern with some codes.

Community
  • 1
  • 1
I Love Stackoverflow
  • 6,738
  • 20
  • 97
  • 216

3 Answers3

4

The best way to do it is with token authentication. In summary it works like this:

  • A POST /api/login route on the server takes in a username + password, checks that they are valid against the database, then generates and returns a refresh token (which can just be a random string or GUID). The refresh token is also stored in the database next to the user, overwriting the previous refresh token
  • A GET /api/access-token route on the server takes in a username + refresh token, checks that they match in the database, then generates and returns an access token
  • Any other /api/* routes require a valid access token to be in the header of the request, otherwise they assume the user does not have a valid login

The access token is a data object that has been encrypted using a secret key that only the server knows. It should contain the username, an expiry date (usually ~10mins from when the token was generated), and any permissions or misc data about the user. Because it is encrypted with a secret key, it cannot be forged by an attacker.

You will need to implement these routes on your server.

If you are using OWIN, here is how you can use the Microsoft.Owin.Security.OAuth NuGet package to do the encryption bit for you:

Have this in your startup configuration:

using System.Web.Http;
using Microsoft.Owin;
using Microsoft.Owin.Security.OAuth;
using Owin;

[assembly: OwinStartup(typeof(MyProject.Startup))]
namespace MyProject
{
    public class Startup
    {
        public static OAuthBearerAuthenticationOptions OAuthBearerOptions { get; private set; }

        public void Configuration(IAppBuilder app)
        {
            var config = new HttpConfiguration();

            OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
            app.UseOAuthBearerAuthentication(OAuthBearerOptions);

            // Configure Web API to use only bearer token authentication.
            config.SuppressDefaultHostAuthentication();
            config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));

            app.UseWebApi(config);
        }
    }
}

Then, you can generate a ticket (which is the unencrypted access token) and encrypt it like this:

var identity = new ClaimsIdentity(new[] {
    new Claim(ClaimTypes.Email, "users email"),
    // other business specific claims e.g. "IsAdmin"
});
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties(
    {
        ExpiresUtc = DateTime.UtcNow.AddMinutes(10)
    }));
var accessToken = MyProject.Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);

In Angular you need to setup a way to login, a way to get a new access token when it expires, and a way to pass the access token in the header of every API request. I recommend storing the refresh token and access token in local storage (or cookie for old browsers), and using $httpProvider.interceptors.push to add an interceptor for every $http call. The interceptor can then add the access token to the header like this: config.headers['Authorization'] = 'Bearer ' + accessToken;

Define the interceptor in angular:

angular.module('app').service('authenticationInterceptor', ['$q', function($q) {
    this.request = function(config) {
        var accessToken = // ... get the access token from local storage / cookie
        config.headers['Authorization'] = 'Bearer ' + accessToken;
        return config;
    };
}]);

Add it to $httpProvider:

angular.module('app').config(['$httpProvider', ($httpProvider: ng.IHttpProvider) => {
    $httpProvider.interceptors.push('authenticationInterceptor');
}]);
Mike Jerred
  • 9,551
  • 5
  • 22
  • 42
  • can you please provide angular js code too for what you are saying with using $httpProvider.interceptors.push and config.headers['Authorization'] = 'Bearer ' + accessToken; – I Love Stackoverflow Mar 23 '16 at 03:22
  • 1
    I should also note that if you are using a load balanced setup then you need to make sure that the secret key is the same on all servers. This is done with the `machineKey` entry in web.config see https://msdn.microsoft.com/en-us/library/ff649308.aspx – Mike Jerred Apr 06 '16 at 01:04
  • Your approach of interceptor is quite good but i am little confuse of how to use this interceptor with my every http call.you have any demo project which illustrate your concept that would be more helpfull.can you please share it or any link from where i can get this??? – I Love Stackoverflow Apr 06 '16 at 03:18
  • 1
    sorry I don't have any demo, it's from propriety software I built. If you register with `$httpProvider` as at the bottom of my answer then it will do it for every `$http` request (including ones from `$resource`) – Mike Jerred Apr 06 '16 at 11:02
  • ok i will tell you what i have understand up till now:First step is to create interceptor let say interceptor.js file in which we will write interceptor code like this one:angular.module('app').service('authenticationInterceptor', ['$q', function($q) { } right.Now lets say i have index.cshtml page which is my login page and i have one js file call as authentication.js file so now what to do sir?? – I Love Stackoverflow Apr 07 '16 at 04:46
  • 1
    Add the `angular.module('app').config(['$httpProvider'` code to any js file you have: `.config` sesctions are called by angular during startup. – Mike Jerred Apr 07 '16 at 19:02
  • Sir just 1 request that can you please show this in your answer step by step like :step 1 create Interceptor.js file and the code to place in this interceptor.js file and most importantly where to put this interceptor.js file on master page(.layout.cshtml) or on individual page.Step 2:create login.js file and how to use this interceptor in my login.js file.Can you just help me with this please – I Love Stackoverflow Apr 08 '16 at 03:24
  • @Mike, quick question, when we send the 'access token' in the header (using the interceptor), how do we validate the access token is authentic in Web API? – Vasan Jun 28 '16 at 11:33
  • 1
    @Vasan: by doing `app.UseOAuthBearerAuthentication(OAuthBearerOptions)` the OAuth library will automatically require a valid access token on all routes with the `[Authorize]` attribute. You could do it manually using the `OAuthBearerOptions.AccessTokenFormat.Unprotect` method – Mike Jerred Jun 28 '16 at 17:14
3

This is a very broad question to answer and there is no chance of doing it in single answer.

What I can say to you is "token authentication" with Outh2 should do the job; that's the easiest way to work with Angular and WebAPI when we talk about security. Cookies are not the right solution in this case.

Check this article for more details on how to implement a full working Token Based Authentication using ASP.NET Web API 2, Owin, and Identity. That article really helped me in many ways. Eventually, when you will have a sample solution, you can came back here and ask for more "details".

BTW, I read this "comment" inside you code:

//Now the tricky parts comes like after authenticating user should i //just store the user id in my session like this Session["userid]=id or //Should i go for storing whole user object in my cookie as i have read

Personally, I think session is evil. I'm not the only one. Anyway, if you want to use "session" in web.api world, it doesn't come out for free, you have to activate it. So if for any reason you need to use it, check this.

Hope it helps :)

Luca Ghersi
  • 3,261
  • 18
  • 32
2

The best way is to use json web token (JWT)...

Satellizer is a great library to add to your Angular app https://github.com/sahat/satellizer

HTML

    <form method="post" ng-submit="login()" name="loginForm">
      <div class="form-group has-feedback">
        <input class="form-control input-lg" type="text" name="email" ng-model="user.email" placeholder="Email" required autofocus>
        <span class="ion-at form-control-feedback"></span>
      </div>
      <div class="form-group has-feedback">
        <input class="form-control input-lg" type="password" name="password" ng-model="user.password" placeholder="Password" required>
        <span class="ion-key form-control-feedback"></span>
      </div>
      <button type="submit" ng-disabled="loginForm.$invalid" class="btn btn-lg  btn-block btn-success">Log in</button>
      <br/>
      <p class="text-center text-muted">
        <small>Don't have an account yet? <a href="/#/signup">Sign up</a></small>
      </p>
      <div class="signup-or-separator">
        <h6 class="text">or</h6>
        <hr>
      </div>
    </form>

JS

angular.module('MyApp')
  .controller('LoginCtrl', function($scope, $auth) {

     $scope.login = function() {
        $auth.login($scope.user)
          .then(function() {
            toastr.success('You have successfully signed in!');
            $location.path('/');
          })
          .catch(function(error) {
            toastr.error(error.data.message, error.status);
          });
        };    
  });

Server-side, they have a C# example at https://github.com/sahat/satellizer/tree/master/examples/server/c%23

malix
  • 3,566
  • 1
  • 31
  • 41