Authentication of an Angular2 application with JWT

Posted by nerdcoding on November 26, 2016

In my last blog post we wrote an Spring Boot application which provided some RESTful web services only callable by authenticated clients. Now we will create the client part in form of an Angular2 single page application. The only not secured web service on the server is the LoginRestController. We will call this web service with some credentials and if the authentication was successful, the web service will return an JSON Web Tokens (JWT). For all subsequent REST calls from our Angular2 app, this JWT needs to be provided. If the JWT is still valid, the server will interpret this calls as authenticated.

A full working example could be found at https://github.com/nerdcoding/angular2-spring-boot. This example consists of the Spring Boot server application and the Angular2 client described in this tutorial. You can ignore the server application if you are only interested in the client part.

How JSON Web Token (JWT) works

A RESTful web service allows manipulation of resources by a defined set of stateless operations. Because of the stateless aspect all information regarding to a session state are hold on the client. The client need to manage all session information by itself and with any service request necessary information have to be provided in that request. That means there is never some form of server side session.

But how the server is able to distinguish between authenticated and not-authenticated users? That’s were the JSON Web Token (JWT) comes into play. JWT is an open standard (RFC 7519) that defines a token which could be transferred between two parties. In our case the client provides normal login form and with the entered username and password the LoginRestController is called. If the provided credentials are valid, the web service will return with an JWT. We need to store this token and use it for all subsequent web service calls.

Login component

The login is done with an standard Angular2 component. We define a HTML form with input fields for username and password.

login.html
<form [formGroup]="form" (ngSubmit)="login()" novalidate>

   <div class="form-group">
       <label for="inputUserName" class="sr-only control-label">User name</label>
       <input type="userName" id="inputUserName" class="form-control" placeholder="user name"
              formControlName="userName" autofocus>
       <div [hidden]="form.get('userName').untouched
                        || !form.hasError('required', 'userName')"
            class="alert alert-danger">
           Please enter a valid user name
       </div>
   </div>

   <div class="form-group">
       <label for="inputPassword" class="sr-only">Password</label>
       <input type="password" id="inputPassword" class="form-control" placeholder="Password"
              formControlName="password">
       <div [hidden]="form.get('password').untouched
                        || !form.hasError('required', 'password')"
            class="alert alert-danger">
           Password is required
       </div>
   </div>

   <button class="btn btn-lg btn-primary btn-block" type="submit">Log in</button>
</form>

The associated login.component.ts takes the provided username and password during a form submit and makes an HTTP POST to the the servers RESTful web service LoginRestController. If the authentication was successful, the server sends a new JSON Web Token (JWT) in the response and we save this token in the HTML local storage.

login.component.ts
@Component({
  templateUrl: './app/components/login/login.html'
})
export class LoginComponent {

  private serverUrl = 'http://127.0.0.1:8080/';

  private form: FormGroup;

  constructor(private router: Router, private http: Http) {
      this.form = new FormGroup({
          'userName': new FormControl('', Validators.required),
          'password': new FormControl('', Validators.required)
      });
  }

  login() {
      console.log(this.form.value);
      if (this.form.valid) {
          this.http.post(this.serverUrl + '/login',
                    { "username": this.form.value.userName,
                      "password": this.form.value.password })
              .toPromise()
              .then(response => {
                  var jwToken = response.json().jwtToken;
                  localStorage.setItem('id_token', jwToken);

                  this.router.navigate(['overview']);
              }).catch(this.handleError);
      }
  }

angular2-jwt

After an successful authentication, the JSON Web Token (JWT) is available in the clients local storage. For all subsequent requests to the server we need to provide this token into an HTTP request header. This could be done with angular2-jwt.

angular2-jwt is a small helper library which helps us to work with JWT. The installation could be done with an simple:

npm install angular2-jwt --save

Anymore angular2-jwt needs to be mapped in the systemjs.config.js:

systemjs.config.ts
map: {
    [...]
    'angular2-jwt':              'node_modules/angular2-jwt/angular2-jwt.js'
}

In the module configuration we also configure how angular2-jwt should find and handle the JWT:

app.module.ts
    providers: [

        [...]

        provideAuth({
            headerName: 'X-AUTH-TOKEN', (1)
            headerPrefix: ' ',
            tokenName: 'id_token',
            tokenGetter: (() => localStorage.getItem('id_token')),  (2)
            globalHeaders: [{'Content-Type': 'application/json'}],
            noJwtError: false,
            noTokenScheme: false
        })
    ]
  1. When a RESTful resource is called we need to send the JSON Web Token inside of an HTTP header. Here we define that the name of this header should be X-AUTH-TOKEN.

  2. Our login.component.ts saved the JWT inside of the HTML local store with the key id_token. Here we tell angular2-jwt where this token could be found.

Finally we need to add AuthHttp provided by angular2-jwt. Each HTTP requests needs to send the JWT inside of an HTTP header. For all HTTP requests we do not use Angular2 default HTTP class, but the AuthHttp. This class will find the token in the local storage and send it with an HTTP header. Therefore each of our serves which make the actual HTTP requests need to use AuthHttp.

person.service.ts
import {AuthHttp} from "angular2-jwt";

@Injectable()
export class PersonService {

    private url = 'http://127.0.0.1:8080/persons';

    constructor(public authHttp: AuthHttp) {}

    getPersons(): Promise<Person[]> {
        return this.authHttp.get(this.url)
            .toPromise()
            .then(response => response.json() as Person[])
            .catch(this.handleError);
    }
}

Routing

When there is no valid JWT in our local storage the server will responds to each HTTP requests with an 401. To prevent that our client sends requests when we are not logged in, we configure our routing to always navigate to the login component.

To check if we are logged in, that means if an valid JWT exists, we can write a small Angular2 service.

person.service.ts
@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private router: Router) {}

    canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        if (tokenNotExpired()) {
            return true;
        }

        this.router.navigate(['login']);
        return false;
    }
}

In this service we implement the CanActive interface. With the help of this interface the Angular2 router can decide if an route is accessible. The canActivate() methods checks if there is a valid JWT, and if not we always navigate to the login component. In the routing configuration we set the AuthGuard at each route.

app.routing.ts
const routes: Routes = [
    {
        path: '',
        redirectTo: '/overview',
        pathMatch: 'full'
    },
    {
        path: 'login',
        component: LoginComponent,
    },
    {
        path: 'overview',
        component: PersonOverviewComponent,
        canActivate: [AuthGuard]
    },
    [...]
];
export const routing = RouterModule.forRoot(routes);