How to implement JWT authentication in Spring Security and Angular – Part 4

Now that we’ve secured the backend with Spring security and implemented the basic JWT features, it’s time to allow users to log in from a user interface. In this example, I’ll show you how to implement a very basic authentication form in an Angular frontend. The pages, that may only be visited by authenticated users, will be protected by a special guard. For that purpose, I’ll show you how to implement such a guard and how to use the Angular router to redirect unauthenticated users to the login page.

But first, let’s start by creating a very basic login page:

Creating the login page

Start by generating a new component with the Angular CLI. Navigate to your components folder and type

ng g c login

This will create the necessary files for the new login component and correctly add it to your existing app. If you’re following this tutorial from scratch (and don’t have a project that you want to extend), create a new component that gets displayed to logged in users:

ng g c secured

And while you’re at it, install jwt-decode:

npm install jwt-decode

Once that’s done, open the login component’s HTML file in an editor of your choice and add the following lines of code to it:

<div class="d-flex justify-content-center">
    <!-- the login form -->
    <div class="loginFormContainer" id="loginForm">
          <div class="form-group">
            <label for="usernameTBox">Username</label>
            <input [(ngModel)]="username" type="text" class="form-control" id="usernameTBox" placeholder="">
          </div>
          <div class="form-group">
            <label for="passwordTBox">Password</label>
            <input [(ngModel)]="password" type="password" class="form-control" id="passwordTBox" placeholder="">
          </div>
          <button type="button" class="btn btn-primary" (click)="loginUser()" id="loginButton">Login</button>
    </div>
</div>

Then, in the component’s typescript file, add the following two variables:

private username : string;
private password : string;

For this to work, you’ll need to update your app.module.ts file. In it, import the FormsModule and HttpClientModule component and add it to the imports:

// Other imports
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  // ...

  imports: [
    // ...
    FormsModule,
    HttpClientModule
  ],

  // ...
})
export class AppModule { }

After you performed these steps, add a route for the login (and optionally the secured) component to your app routing module:

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'secured', component: SecuredComponent }
];

Don’t worry about the routes too much for now. We’ll change these later to block access to the secured component for unauthorized users as well as add some default routes.

Anyway, when you made all the changes from above, you should be able to navigate to the login-page (localhost:4200/login).

Figure 1: The very basic login page of the application

Implementing the login functionality

Now that we’ve created a basic login page, it’s time to implement its functionality: The password and username get sent to the backend which then responds with a JWT. This JWT then has to be included with each request that the frontend sends to the backend.

Let’s start by creating two DTOs for the JWT request and response:

export class TokenRequest
{
    username: string;
    password: string;
    
    constructor() {}
}

And the response:

export class TokenResponse
{
    token: string;
    
    constructor() {}
}

Then, create a service that’ll be used to send messages to and receive responses from the backend:

export class AuthenticationService
{
  private baseUri: string = 'http://localhost:8080/authentication';

  constructor(private httpClient : HttpClient)
  { }

  public getToken(credentials : TokenRequest) : Observable<TokenResponse>
  {
    return this.httpClient.post<TokenResponse>(this.baseUri + "/", credentials);
  }

  public getLoggedInUserName() : string
  {
    const decoded: any = jwt_decode(this.getStoredToken());
    return decoded.sub;
  }

  setStoredToken(authResponse : TokenResponse)
  {
    localStorage.setItem("token", authResponse.token);
  }

  getStoredToken()
  {
    return localStorage.getItem("token");
  }

  logOut(error : boolean)
  {
    localStorage.removeItem("token");
  }
}

The only really interesting function is the getToken function. It’ll send the credentials, that a user entered on the login page, to the backend and receive a token if the credentials were correct.

Important note: The password is stored and passed along in cleartext. This must not be the case in a real-world application for security reasons.

However, now that the service is implemented, it’s time to link it to the login page, we created earlier. For that, open the login’s TS file and add the following function:

loginUser()
{
  // Validate input

  let credentials : TokenRequest = new TokenRequest();
  credentials.username = this.username;
  credentials.password = this.password;

  this.authenticationService.getToken(credentials).subscribe(
    (response: TokenResponse) => {
      if(!(null === response.token))
        {
          console.log("Login successful!");
          this.authenticationService.setStoredToken(response);
          this.router.navigateByUrl("/secured");
        }
        else
          console.log("Wrong credentials!");
    },
    error => {
      console.log("Error logging in user " + this.username + ": " + error.error);
    }
  );
}

Also, don’t forget to add the service and the router to the constructor:

constructor(private authenticationService : AuthenticationService, private router : Router)
{ }

As you might remember, the backend will always generate an answer to a token request, even when the credentials are wrong or the user doesn’t exist in the database. However, in such a case, the generated token will be null. I’ll address this in the last part of the series and implement better error handling. For now, that’s good enough as it is.

When you made the changes, discussed in this section, you can already use the login functionality as intended. Create a user, as described in part three of this series, and then try logging in.

Creating guards to secure pages

Even though the login works as intended, the secure-component can still be accessed without logging in first. That should, obviously, not be the case. For that reason, we’ll create a guard. Such a guard will simply check, whether a specified condition is true and it will then tell the router to continue and serve the requested page, if the condition is met.

import {Injectable} from '@angular/core';
import {CanActivate, Router} from '@angular/router';
import {AuthenticationService} from '../services/authentication.service';

@Injectable({
  providedIn: 'root'
})

export class AuthenticationGuard implements CanActivate
{
  constructor(private authenticationService: AuthenticationService, private router: Router)
  {}

  canActivate(): boolean
  {
    if (this.authenticationService.isLoggedIn())
    {
      return true;
    }
    else
    {
      this.router.navigate(['/login']);
      return false;
    }
  }
}

As you can see, the guard simply calls the authentication service’s isLoggedIn()-function that’ll check, whether a token is set in the local storage and whether it’s not expired.

For the guard to protect specific pages, it has to be included in the app-routing module:

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: 'secured', canActivate: [AuthenticatedGuard], component: SecuredComponent },
  { path: '', redirectTo: '/login', pathMatch: 'full' }
];

Now, whenever a user tries to access the secured-module, the guard will be activated. When the guard returns true, access is granted. Otherwise, the user gets redirected to the login page.

Creating a logout button

To try the guard from above, we have to be able to log out. For that purpose, add a button to the secured page:

<p>You are now logged in!</p>
<button type="button" class="btn btn-primary" (click)="logoutUser()" id="loginButton">Logout</button>

Then, in the TypeScript of that module, add the following method:

logoutUser()
{
  this.authenticationService.logOut(false);
  this.router.navigateByUrl("/login");
}

Note that this is just a quick and dirty demonstration of the logout feature.

Requesting secured resources from the backend

So, now that the frontend is secured, we’d like to access the users-list we secured in the backend earlier in this series. As you know, you’ll need to provide a valid bearer token to access the list.

That means, that you’ll need to supply an authorization field in the HTTP request you send to the backend. However, instead of defining the resources, that need such a header, I’ll instead add the header to all requests and exclude a few, that don’t need the header. An example is a POST request to the authorization resource. It obviously doesn’t need a JWT because you are trying to get one.

In Angular, interceptors are used to modify http requests and responses. (Remember how we used the same technique in the backend earlier in this series to parse the token?). For that purpose, create a file called “index.ts” in a new folder called “interceptors”:

import {HTTP_INTERCEPTORS} from '@angular/common/http';
import {AuthenticationInterceptor} from './authentication-interceptor';

export const httpInterceptorProviders = [
  { provide: HTTP_INTERCEPTORS, useClass: AuthenticationInterceptor, multi: true }
];

And then define our custom interceptor itself:

import {Injectable} from '@angular/core';
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {AuthenticationService} from '../services/authentication.service';
import {Observable} from 'rxjs';

@Injectable()
export class AuthenticationInterceptor implements HttpInterceptor
{
  constructor(private authService: AuthenticationService)
  { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authUri = 'http://localhost:8080/authentication/';
    const createUserUri = 'http://localhost:8080/users/';

    // Ignore requests to the authentication endpoint
    // Because the authentication service handles headers for those requests
    if (req.url === authUri)
      return next.handle(req);

    if(req.url === createUserUri && req.method === "POST")
      return next.handle(req);

    const authReq = req.clone
    (
      {
        headers: req.headers.set('Authorization', 'Bearer ' + this.authService.getStoredToken())
      }
    );

    return next.handle(authReq);
  }
}

As described, the header gets added to all requests except for the ones, that don’t require it. In this case, these are all calls to the authentication resource and POST calls to users (for creating new users).

To apply interceptors, add the following entries to the providers of your app.module.ts:

providers: [
    httpInterceptorProviders,
    HttpClient
],

Creating a user-list component

Note: This section will be very short because I assume you know how to create components and access a backend with Angular by this point.

Now, to test our finished application, create a a new component that simply lists the users, stored in the database. The component’s HTML file only contains one line:

<p *ngFor="let user of retreivedUsers; index as i">{{user.username}}</p>

The rest is handled by the corresponding TypeScript file.

Then, just like before, create a DTO and a service that accesses the backend. I omitted those two steps, to keep this article shorter. However, you can download the completed project in the last series of this project, if you’re not sure how to implement the service and DTO.

Then, add a new route to that component and secure it with the guard, we created earlier.

Once that’s done, log in as a user in the frontend and navigate to the newly created user-list component:

Figure 2: The list of users as returned by the backend

And this is how you can access a secured resource.

In the next part…

… we’ll finished this project. Right now, a logged in user’s token will simply expire, even though he is still active in the frontend which will lead to errors. So in the last part of the series we’ll implement a token renewal mechanism that automatically requests a new token from the backend shortly before the user’s token expires.

Furhtermore, I’ll implement better error handling. Right now, the backend server will run into a null pointer exception when the supplied token is invalid. It will also return null as a token instead of throwing an HTTP error. Both of these will also be fixed in the last part.

Table of contents

Part 1 – Basics of JWT and the project scaffolding
Part 2 – Configure Spring Security
Part 3 – JWT token generation, validation, and authentication
Part 4 – JWT authentication in an Angular frontend (You are here)
Part 5 – Token renewal

4 thoughts on “How to implement JWT authentication in Spring Security and Angular – Part 4

Leave your two cents, comment here!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.