If you've got an Angular app that relies on Spring Boot to do the heavy-lifting on the back-end side, then you're probably going to need some kind of login solution. Fortunately, you can do that with a JSON Web Token (JWT).

I've already written a guide on how to deploy a Spring Boot solution that uses JWT. In this article, I'll just add Angular to the mix.

By the way, that means I won't be rehashing everything I covered in the previous article. If you'd like to get an in-depth lesson on Spring Boot and JWT, be sure to read that post first.

If you're really strapped for time (aren't we all?), just grab the code from GitHub. You can find the Spring Boot branch here and the Angular branch here.

Remember, though: if you're having problems running the Angular app locally, be sure to check the README.

Please note: this guide is a part of an ongoing series of tutorials on how to create a customer relationship management (CRM) application from scratch. The GitHub code for each guide belongs to a specific branch. The master branch holds the entire application up to the current date. If you want to follow the whole series, just view the careydevelopmentcrm tag. Note that the website displays the guides in reverse chronological order so if you want to start from the beginning, go to the last page.

The Business Requirements

Your boss Smithers walks into your office and says that he needs a security solution for that CRM application you're working on.

Specifically, he says that he likes "that J-W-T thing" because that's what all the kids are using these days.

He wants you to create a login solution that enables users to stay logged in to the CRM application for 24 hours. After that, they have to login again.

Also, Smithers tells you that he's got more applications in the pipeline. He wants the same user credentials to work for all of those applications.

"What we're trying to do is develop an equal system," he says.

You know he means ecosystem.

Smithers wants you to deploy a user application that all the other applications in the ecosystem can share.

"It's going to be part of our pico-services architecture," he says.

You know he means microservices architecture.

Migrating the Code

You've already skinned this cat on the Spring Boot side.

So now you need to do the same thing you did before but enable users to login from within an Angular application.

That means you'll start by migrating code. In this case, you'll migrate code from the old project.

Remember, though, when you copy code from one project to another, you often need to change package names. And that's exactly what you need to do here.

Specifically, here are the classes you need to copy:

  • JwtAuthenticationEntryPoint
  • JwtRequestFilter
  • WebSecurityConfig
  • JwtAuthenticationController
  • JwtRequest
  • JwtResponse
  • User
  • JwtUserDetailsService
  • JwtTokenUtil

Of course, I've already done all of that for you. 

If you don't want to migrate those classes manually, just grab the code from the GitHub branch.

A Few Tweaks

If you copied the code from the old project, you'll need to make a few tweaks. If you just grabbed the branch from GitHub, the tweaks are already in place.

In either case, I'll go over the tweaks so you know why they're necessary.

Here's an update to JwtTokenUtil:

private static JwtTokenUtil doGenerateToken(Map<String, Object> claims, String subject) {
	String token= Jwts.builder()
				.setClaims(claims)
				.setSubject(subject)
				.setIssuedAt(new Date(System.currentTimeMillis()))
				.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY))
				.signWith(SignatureAlgorithm.HS512, SECRET).compact();
		
	JwtTokenUtil jwtUtil = new JwtTokenUtil(token);
	return jwtUtil;
}

In the past, that method returned the token as a String object. Now, it's returning an instance of the JwtTokenUtil class.

The reason for that is so you can not only get the token, but also information about the token a little more easily. In this solution, you'll need to get the token expiration date.

And that brings me to the update in the JwtResponse class:

public class JwtResponse {

	private final String token;
	private final User user;
	private final Long expirationDate;

	public JwtResponse(String token, User user, Long expirationDate) {
		this.token = token;
		this.user = user;
		this.expirationDate = expirationDate;
	}
	
	public Long getExpirationDate() {
        return expirationDate;
    }

    public String getToken() {
		return this.token;
	}

	public User getUser() {
		return user;
	}	

	public String toString() {
		return ReflectionToStringBuilder.toString(this);
	}
}

I've added the expirationDate field. You might be surprised to see that it's stored as a Long.

I prefer to store date/time objects as numbers. It's much easier to move them around between different frameworks that way.

Here, the Long number represents the number of milliseconds since Jan. 1, 1970.(GMT). You'll get that value from any java.util.Date object when you invoke the getTime() method.

Note that the constructor requires the expiration date as well. That brings me to the updated code in the controller:

@RestController
@CrossOrigin
public class JwtAuthenticationController {

	@Autowired
	private AuthenticationManager authenticationManager;

	
	@PostMapping(value = "/authenticate")
	public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
		final User user = authenticate(authenticationRequest);
		final JwtTokenUtil jwtTokenUtil = JwtTokenUtil.generateToken(user);
		final String token = jwtTokenUtil.getToken();
		Long expirationDate = jwtTokenUtil.getExpirationDateFromToken().getTime();
		
		return ResponseEntity.ok(new JwtResponse(token, user, expirationDate));
	}

	
	private User authenticate(JwtRequest request) throws Exception {
		User user = null;
		
		try {
			Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
			user = (User)auth.getPrincipal();
		} catch (DisabledException e) {
			throw new Exception("USER_DISABLED", e);
		} catch (BadCredentialsException e) {
			throw new Exception("INVALID_CREDENTIALS", e);
		}
		
		return user;
	}
}

First of all, pay attention to the @CrossOrigin annotation at the beginning. You didn't need to include that in the last guide when you were testing with Postman. But you'll need it here.

Also, the code now gets an instance of JwtTokenUtil when it invokes the doGenerateToken() method. Then, it uses that object to get both the token and its expiration date.

Finally, it instantiates the response with the token, the user, and the expiration date. That's the response returned to the Angular app.

Save everything and deploy. Once again, test it out on Postman just to make sure everything is working as you expect. You can see the previous guide for info on how to test with Postman.

How About a Negative Test?

Here's another test you can perform with your new Spring Boot application up and running.

Go to a browser and hit the following URL: http://localhost:8080/helloworld

You should see something like this:

Whoa! What happened?!? That worked just one article ago!

What happened is you added security. So now that same request shouldn't work.

On to Angular

If you followed the second guide in this series, then you've already integrated Angular with Spring Boot. You did the "Hello, World!" thing when invoking the Spring Boot REST service from within Angular.

If you haven't done any integration between Angular and Spring Boot yet, then I strongly recommend you check out that article.

For the purposes of this guide, all you need to do is the same thing as the "Hello, World!" integration except now you're getting a JSON Web Token. Then, you'll use that token to make subsequent requests.

Here, all I'll focus on is getting that JSON token. I'll cover the more elaborate parts of user login later on.

Start by creating the models you'll use on the Angular side. Create a folder called models under the app folder. 

Then, create a file called user.ts in that folder. Unsurprisingly, that will be your user model.

Here's what it will look like:

export interface User {
	id: string
	firstName: string;
	lastName: string;
	street1: string;
	street2: string;
	city: string;
	state: string;
	zip: string;
	email: string;
	phoneNumber: string;
	authorityNames: string[];
	username: string;
	country: string;
	profileImageFileName: string;
}

A couple of things to note here.

First, the code uses an interface instead of a class.

Why? Because all you need to do with this model is handle type-checking. 

If the instance called for methods that handle business logic, I would probably have chosen a class.

In fact, I might change this to a class at some point in the future. But for now an interface is fine.

Next, you'll see that the interface includes most of the properties of its counterpart on the Spring Boot side of the house. That's perfectly normal.

And it also sucks.

When you're going this route, you have to duplicate code. Some day, I'm sure someone will come up with a workaround, but for now get used to the idea of duplicating models on both sides of the house.

Next, create the model that reflects the JSON Web Token request:

export interface JwtRequest {
    username: string;
    password: string;
}

Yep. That's another dupe.

And so is this:

import { User } from './user';

export interface JwtResponse {
    token: string;
    expirationDate: number;
    user: User;
}

The response you get back from Spring Boot will include the token, the token's expiration date, and user info. You'll need all of that so you capture it in the JwtResponse type.

The Authentication Service

Now that you've got the supporting models in place, it's time to move on to the service.

Create a new file under the services folder. Call it authentication.service.ts.

There's quite a bit you need to include in the authentication service so I'll go over it a little bit at a time. First, the imports.

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { JwtRequest } from '../models/jwt-request';
import { JwtResponse } from '../models/jwt-response';
import { tap, shareReplay } from 'rxjs/operators';
import { DateService } from './date.service';

The first three imports I covered in the previous guide. No need to go over them again.

The next two imports are the request and response models you just created. 

Now, what about that tap and shareReplay business? What's going on there?

Those are operators (not classes) from the rxjs library. I'll explain how to use those when I go over the relevant lines of code.

Next is a DateService class that I created as a gift to you. It includes a variety of convenience methods for parsing dates.

Now, let's take a look at the first few lines of code:

@Injectable({ providedIn: 'root' })
export class AuthenticationService {

    constructor(private http: HttpClient, private dateService: DateService) {}

I covered the @Injectable annotation in the previous guide. 

I named this class AuthenticationService because that's pretty descriptive. It also agrees with the file name.

The constructor looks similar to the constructor from the last guide. One difference is that it injects the DateService singleton as well. You'll see why in a moment.

Next, take a look at the login() function:

    login(username: string, password: string): Observable<JwtResponse> {
        let jwtRequest: JwtRequest = { username: username, password: password };

        return this.http.post<JwtResponse>('http://localhost:8080/authenticate',
            jwtRequest).pipe(
                tap((resp: JwtResponse) => this.setSession(resp)),
                shareReplay()
            );
    }

The function accepts two parameters: username and password. Those variable names are fairly self-explanatory.

As is the case with the "Hello, World!" example I covered in the previous guide, the login() function returns an Observable. However, this time it returns an Observable of a specific type.

In this case, that type is a JwtResponse object. That makes sense because that's exactly the kind of response you'll get back from the Spring Boot application.

The first line in the method creates an object that must conform to the JwtRequest interface. Once again, that makes sense because that's exactly what the Spring Boot service is expecting.

Yes, it's kind of weird that the property names and the variable names are the same. But on the other hand it makes it pretty easy to figure out what's going on there.

There's quite a bit going on in those next few lines. I'll break them down one by one.

First of all, note that the code is invoking the post() function on the HttpClient object. That's different from the last lesson when you used the get() function.

You won't be shocked to learn that the post() function makes an HTTP POST call while the get() function makes an HTTP GET call.

The post() function needs more than just a URL, though. It also needs a body.

It needs to POST something, after all.

For the purposes of this POST, the body is the JwtRequest object the code instantiated in the previous line. That's why you see it as the second parameter in the post() function.

The first parameter is the URL itself.

Okay, so what is pipe() and that other stuff? That answer requires its own subheading.

rxjs Operators

I said in the previous guide that rxjs operators will become your very best friends. And I meant it.

Like good friends in real life, though, they can sometimes become complicated.

So what's that pipe() thing?

It's a function that allows you to connect a series of functions that return Observables.

Inside the pipe(), you'll find one or more of what we call pipeable operators. Think of them as actions you can take on the Observable that gets sent back to the subscriber.

So what's inside this pipe()? A couple of operators.

First up is tap(). Unusual name, amazing results.

According to the official docs, it "transparently performs actions or side-effects." In this case, it's invoking the setSession() function.

Spoiler: the setSession() function stores the token and its expiration date in localStorage. I'll cover it more in a moment.

Next up is shareReplay() with no parameters. What does that do?

It's a nifty little operation that handles a situation when the observable has multiple subscribers. If there's a johnny-come-lately in the mix, I want that last subscriber to get the last value emitted by the Observable.

Moreover, I want it to do that without invoking another network call.

In a nutshell, that's what shareReplay() does.

I'll dig deeper into these rxjs operators in future guides.

The Rest of the Service

Now, take a look at the next function in the service:

    private setSession(authResult: JwtResponse) {
        const expiresAt = authResult.expirationDate;
        console.log("Token expires at " + expiresAt);
        console.log("Token date and time is " + this.dateService.getShortDateAndTimeDisplay(expiresAt));

        localStorage.setItem('id_token', authResult.token);
        localStorage.setItem("expires_at", JSON.stringify(expiresAt.valueOf()));
    }

I've already telegraphed what this function does. It accepts the JwtResponse object returned from the Spring Boot service as a parameter and uses it to get the token and the expiration date.

Then, it stores both of them in localStorage.

And what is this localStorage thing? It's well-named.

With localStorage, you store data in the browser. But it's not stored as a cookie!

That last sentence is important because it means when you use localStorage you'll avoid some of the security vulnerabilities you would notice when you use cookies.

You can also store quite a bit of data in localStorage: up to 10 Mb.

So yes I think it's a great idea to store both the token and the expiration date in localStorage.

Also note that the setSession() function does some logging.

First, it logs the expiration date as a number. But that's not really helpful is it?

That's why the second log exists. It uses DateService to parse the number and get you a pretty date/time output.

And here's the rest of the code for the service:

    logout() {
        localStorage.removeItem("id_token");
        localStorage.removeItem("expires_at");
    }

    public isLoggedIn(): boolean {
        return Date.now() < this.getExpiration();
    }

    isLoggedOut(): boolean {
        return !this.isLoggedIn();
    }

    getExpiration(): number {
        const expiration = localStorage.getItem("expires_at");
        const expiresAt = JSON.parse(expiration);
        return expiresAt;
    }    

Those functions are fairly self-explanatory.

Revisiting the App Component

You used the app component to run the "Hello, World!" test in the last guide. You'll do something similar here.

Open up app.component.ts for editing. Then, update it to invoke the Spring Boot service for authentication.

The code will look like this:

import { Component } from '@angular/core';
import { AuthenticationService } from './services/authentication.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {

    constructor(private authenticationService: AuthenticationService) {
        let user$ = authenticationService.login("johnny", "kleptocracy");

        user$.subscribe(
            (data: any) => console.log(data),
            err => console.error(err)
        );
    }

  title = 'careydevelopmentcrm';
}

The big difference here is that you're importing AuthenticationService instead of UserService as you did with the previous guide. Then, you're invoking the login() function on AuthenticationService.

That function requires two parameters: username and password. To keep things simple, those credentials are hardcoded as you see above.

The login() function returns an Observable. So you need to subscribe to it before it does anything.

That's why you need to include the subscribe() method that you see in the next line.

As is the case with the previous guide, the code just logs the response it gets from the remote service.

Testing It Out

Okay. Time to play.

If you haven't done so already, launch your Spring Boot application.

Then, launch your Angular application.

After that, fire up your Google Chrome browser and get the Developer Console running on the right-hand side. 

Now, visit the following URL: http://localhost:4200

Take a look at your console on the right. You should see some logging that looks like this:

And that would be a resounding success because that is, in fact, what you are looking for.

Congratulations! You have a successful login solution!

Of course, you still need a front-end solution to enable end users to login. You also need to check token expiration. But I'll go over those requirements in future guides.

Wrapping It Up

Now it's your turn to tinker with the code. Ask yourself: "What would happen if I did this?" and then do it. And see what happens.

It's all on you now. I'll see you at the next guide.

Have fun!

Photo by Micah Williams on Unsplash