Now that we’ve secured the backend with Spring security and implemented all the necessary 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 add them 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 Angular displays 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:
<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 components:
// 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).

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 subsequent 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");
}
}
Here, the only interesting part is the getToken function. It sends the user-supplied credentials from the login page to the backend and receives a token if the credentials were correct.
Important note: The password is stored and passed along unencrypted. This must not be the case in a real-world application for obvious security reasons.
Anyway, 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 to log in using the new user’s name and password.
Creating guards to secure pages
Even though the login works as intended, the secured component can still be accessed without logging in first. Obviously, that shouldn’t 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 checks 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 Angular activates the guard. When the guard returns true, access is granted. Otherwise, the router redirects the user 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 file 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 clients need to supply an authorization field in the HTTP request they send to the backend. However, instead of defining the resources that need such a header, I’ll add the header to all requests and exclude a few that don’t need the header. One example is the POST request to the authorization resource. It doesn’t need a JWT because clients request the JWT from that resource in the first place.
You can use interceptors in Angular to modify HTTP requests and responses. (Remember how we used the same technique in the backend earlier in this series to parse tokens?). 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 the custom interceptor:
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 interceptor adds the authorization header 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 the users resource (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 REST API with Angular by this point.
Now, to test the finished application, create a new component that lists all 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 complete project in the last part of this series.
Add a new route to that component and secure it with the guard, you created earlier. Once that’s done, log in as a user in the frontend and navigate to the newly created user-list component:

And this is how you can access a secured resource.
In the next part…
… we’ll finish this project. Right now, a logged-in user’s token will simply expire, even though they are still active in the frontend. Expired tokens 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.
Furthermore, 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 instead of throwing an HTTP error in case something goes wrong during token generation. Both of these issues 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”