get-the-solution

Add Token Authorization to AngularJS and WebApi

By
on Regards: .NET Framework; C#;

Protection and authorization for a Single Web Application and a RESTFul Service

Introduction

There exists some ways to implement an authentication into RESTFul Service. For this article I will focus on the HTTP Token Header technique, because its really easy to implement with the JsonWebTokenHandler.(RESTful Authentication, 22.08.2015, http://stackoverflow.com/questions/319530/restful-authentication?lq=1 ).

To keep it short we focus on the practical aspect. The demonstration contains a Single Web Application with AngularJS which is hosted on an ASP.NET MVC page. The RESTFul Service will be represented by an ASP.NET WebApi project. I want to mention that most of the code was built from code snippets which I researched from other blogs or stackoverflow to reduce time. So the example project consists of several code snippets and some custom coding.

As you know the RESTFul Service is stateless. We need to achieve that the WebApi can correctly identify our current requests to a user. There the token, or precisely the Json Web Token, comes into play. Because the returned data is formatted as JSON the term Json Web Token is more common in that subject.

When sending a valid login request to the WebApi, it will issue a token for the logged in user. The SWA will catch the returned token from the login. From now the SWA will add the token to each call into the HTTP Header. The WebApi will check if there exists a JWT and extract the containing user information. This process is visualized in the graphic below.

sts

After associating a user to the request we can permit or deny the request.

{"iss":"Identity Provider","name":"John Doe","admin":true}

The Json Web Token is a JSON-based open standard which contains the user payload a signature and the following important header information’s (JSON Web Token, https://en.wikipedia.org/wiki/JSON_Web_Token, 22.08.2015).

  • Issuer (iss) : The Service which created the jwt
  • Subject (sub) : The subject regarding the token
  • Audience (aud) : Which application can use the token
  • Expiration time (exp): When expires the token
  • Not before (nbf): Don’t use the token before
  • Issued at (iat): When was the token created
  • JWT ID (jti) : The ID of the token

(“JSON Web Token (JWT)”, https://self-issued.info/docs/draft-ietf-oauth-json-web-token.html, 22.08.2015 )

###Frontend – AngularJS

Our AngularJS web application has got an Authentication service which can initiate the login process. It sends the logon and the password to the webapi and saves the returned token. The token will be set to the default Authorization HTTP Header of the httpService (“Setting angularjs $http headers globally“, http://stackoverflow.com/questions/27134840/setting-angularjs-http-headers-globally, 22.08.2015).

 class AuthenticationService {
    static $inject = ["$rootScope", "$http"];

    protected scope: ng.IRootScopeService;
    protected http: ng.IHttpService;

    public IsAuthenticated: boolean = false;
    public Resolve: ng.IPromise<{}>;
    public Token: string;

    constructor($rootScope: ng.IRootScopeService, $http: ng.IHttpService, private $location: ng.ILocationService) {
        this.scope = $rootScope;
        this.http = $http;
        $rootScope.$on("$locationChangeStart", this.LocationStartChangeEvent.bind(this));
    }
    //start login process
    public login(login: Login): ng.IPromise<{}> {
        this.Resolve = this.http.post("http://localhost:5117/api/User/PostLogin", login);
        this.Resolve = this.Resolve.then(this.setLogin.bind(this));
        return this.Resolve;
    }

    public setLogin(data: ng.IHttpPromiseCallbackArg<string>, status: number, headers: ng.IHttpHeadersGetter, config: ng.IRequestConfig): void {
        //save the returned token
        this.Token = data.data;
        this.http.defaults.headers.common['Authorization'] = this.Token;
        this.IsAuthenticated = true;
        //broadcast the authenicated event in our app
        this.scope.$broadcast("IsAuthenticatedEvent", { IsAuthenticated: this.IsAuthenticated, Token: this.Token });
    }
    public LocationStartChangeEvent(event: ng.IAngularEvent, args: any[]) {
        if (this.IsAuthenticated == false) {
            this.$location.path('/login');
        }
    }
}

In order to prevent the user from navigating to any other page of our application, we add a callback to the angular module. When navigating between pages the callback checks if the user is logged in our Authentication service.

This is done by registering the “$locationChangeStart” event which will be fired on any page navigation. If the user is not logged in we force a redirect to the login page. A logout can be done by refreshing the whole page, the state of the SWA will then be lost.

app.run(['$rootScope', '$route', 'AuthenticationService', '$location',
            function ($rootScope: ng.IRootScopeService, $route: angular.route.IRouteService, authenticationService: AuthenticationService, $location: ng.ILocationService) {
                $route.reload();
                if (authenticationService.IsAuthenticated == false) {
                    $location.path('/login');
                }
            }]);

After a successful login we forward the user from the login- to the home view. The corresponding homeController makes an authenticated webapi request which will then return the current users privileges.

###Backend – WebApi

Let’s switch to the WebApi’s PostUser action method. It is used to login a user and issue a security json web token.

First of all we check if the delivered user credentials are correct. Normally this is done by querying the database or another resource. The user and the roles are implemented with the claims-based identity of the Windows Identify Foundation. So if you never heard of claims you should definitely check Andras Nemes blog series about that topic (“Introduction to Claims based security in .NET4.5 with C# Part 4: authorisation with claims”, http://dotnetcodr.com/2013/02/21/introduction-to-claims-based-security-in-net4-5-with-c-part-4-authorisation-with-claims/, 22.08.2015 ).

For creating JSON Web Tokens Microsoft published the JwtSecurityTokenHandler class which can be found in the NuGet Package System.IdentityModel.Tokens.Jwt.

We create the ClaimsIdentity for the user which we wish to log in by adding his claims. After that we can create the token with tokenHandler.CreateToken(issuer: “http://localhost”, audience: “http://localhost”, subject: claimsIdentity, expires: expires, signingCredentials: new X509SigningCredentials(cert));

So as you see the issuer and audience value are equal because we create the token and at the same time consume it. With the expire parameter we can determine how long the token is valid and with the signing credentials we sign our token against manipulation. The returned encoded token is a base64 string.

The Json Web Token will be signed when issuing it with a private key. When a token will be “read”, it will be validated against the public key.

In this project I deposit the private and the public key as files, which is not recommended regarding security issues. In a real world example it would be stored in the certificate manager and for a convenient project start up I skipped that part. By the way, I used the following self-signed certificate tool http://blog.pluralsight.com/selfcert-create-a-self-signed-certificate-interactively-gui-or-programmatically-in-net.


        [HttpPost]
        public IHttpActionResult PostLogin([FromBody]LoginViewModel login)
        {
            if (login != null && login.Logon == "martin")
            {
                //let the token expire in one day
                DateTime expires = DateTime.Now.AddDays(1);

                var tokenHandler = new JwtSecurityTokenHandler();
                //our certificate we use to sign the token
                X509Certificate2 cert = new X509Certificate2(Path.Combine(AssemblyDirectory, "private.localhost.pfx"), "localhost", X509KeyStorageFlags.MachineKeySet);
                //add claims to the user which we want to login
                ClaimsIdentity claimsIdentity = new ClaimsIdentity(new[]
                {
                     new Claim(ClaimTypes.Name, login.Logon),
                     new Claim("custom claim type", "custom content"),
                     new Claim(ClaimTypes.Role, "admin")
                 });
                //create the token
                var token = (JwtSecurityToken)tokenHandler.CreateToken(issuer: "http://localhost", audience: "http://localhost", subject: claimsIdentity, expires: expires, signingCredentials: new X509SigningCredentials(cert));
                var tokenString = tokenHandler.WriteToken(token);
                //return the token
                return Ok<String>(tokenString);
            }
            return new BadRequestResult(this);
        }

We should not only be able to create tokens, but also to read and “extract” the information from it. This can be done with a DelegatingHandler which intercepts every web request of the WebApi.

Vittorio Bertocci wrote an excellent article that topic a while ago. The JwtSecurityTokenHandler has changed since the article was released.

What does the TokenValidationHandler do in particular?

First of all it intercept the web request before the concerning action method gets executed. It checks if the request contains a HTTP Header with the key Authorization. If not, the request will be forwarded to the action method. If you wish to protect the action method you can add a claimsauthorization attribute (details about that later).

If there exists a JWT token it will be validated against the TokenValidationParameters and the public key. With ValidateToken the user information from the token will be extracted and set the current user of the HttpContext and Thread. If the validation didn’t fail the request will be forwarded to the action method. Otherwise we abort the current request and return an unauthorized status code.


            if (!TryRetrieveToken(request, out token))
            {
                //we allow requests with no tokens - forward them to the associated action method 
                return base.SendAsync(request, cancellationToken);
            }

            try
            {
                //get the public key for validation the signed token
                X509Certificate2 cert = new X509Certificate2(Path.Combine(UserController.AssemblyDirectory, "public.localhost.cer"), "localhost");

                SecurityToken securityToken;
                JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
                TokenValidationParameters validationParameters = new TokenValidationParameters()
                {
                    ValidAudience = "http://localhost",
                    ValidIssuer = "http://localhost",
                    IssuerSigningToken = new X509SecurityToken(cert)
                };
                //validate and assign the returned user to the current request
                Thread.CurrentPrincipal = handler.ValidateToken(token, validationParameters, out securityToken);
                HttpContext.Current.User = handler.ValidateToken(token, validationParameters, out securityToken);
                return base.SendAsync(request, cancellationToken);
            }

If for specific action methods certain claims are necessary we can use the ClaimsAuthorization attribute. Before the action method gets executed, it checks if the current HttpContext.User got the required claims.

The order how the web request is processed

###Remarks The solution is configured in a way that you can easily execute the project. Make sure you installed Visual Studio 2013 with the Web Development Tools and the latest updates. Also The Web Essential Tools should be installed with the latest TypeScript Version. You can Download the project from Github.

Press F5 to start the Web Project and navigate to http://localhost:5118/Default/Index/to start the SWA. Then switch to Visual Studio and right click on the WebApi Project and select Debug àStart new Instance. Create some breakpoints and try to login with the user martin in the web application and step through the program to see what’s happening.

Because the web projects are hosted under different domains (http://localhost:5118vs http://localhost:5117) we run into the CORS issue. To solve that I installed the CORS NuGet Package from Microsoft and enabled it with the EnableCors attribute. You may also want to check http://www.asp.net/web-api/overview/security/enabling-cross-origin-requests-in-web-api.

I hope I could show you that it is really is easy to implement an authentication mechanism in AngularJS and ASP.NET WebApi. If you have any questions or suggestion feel free to comment or write me on twitter.

Other used Links:

http://stackoverflow.com/questions/18032038/securing-the-jwt-with-a-x509certificate2-jwtsecuritytokenhandler create jwt token

http://stackoverflow.com/questions/20198124/read-private-key-from-pfx-file read certificate from file

http://stackoverflow.com/questions/52797/how-do-i-get-the-path-of-the-assembly-the-code-is-in get current assemply path