Home | Send Feedback

JWT Authentication with Ionic 4 and Spring Boot

Published: February 05, 2017  •  Updated: December 07, 2018  •  ionic4, spring, java, javascript

JSON Web Token (JWT) is a standard (RFC 7519) for creating access token. A JWT consists of 3 parts: a header, the payload and a signature. The header describes what algorithm is used to generate the signature. The payload contains information like the issuer of the token, the subject, when the token was issued or the expiration date. There are no mandatory information an application has to write into the payload. The example application we build in this article puts the issue and expiration date and the username (subject) into the payload.
The last part of an JWT is the signature that is calculated over the whole message and prevents a malicious party to change the contents of the token.

See a more detailed description of JWT: https://jwt.io/introduction/

In this article we build an Ionic app that talks to a Spring Boot back end. The client app consists of a signup page where the user can create new accounts, a login page where the user enters a username and a password and a secret page that the user sees after a successful authentication. After a successful signup and login the client receives a JWT from the server and stores it locally on the client. Next time the user opens the app he is going to be automatically logged in, if the JWT is still valid.

Sign up


As usual when I create a new Spring Boot application I open the website https://start.spring.io, fill in the fields Group and Artifact and select the required dependencies. For this application we need the Web and Security dependency.

Spring Initializr

After you downloaded the generated code to your local computer, open the file pom.xml and add this dependency.



jjwt is a Java implementation of the JWT standard. It provides classes and methods to create and verify tokens.


We don't connect this application to a real database. It just stores the users in a hash map in memory. The User class is the entity object that represents a user in our application.

public class User {

  private String name;

  private String username;

  private String email;

  private String password;


The class UserService manages the Map of the users. Because the username is unique it serves as the key of the Map. The class provides public methods to lookup and save user objects and a check method that returns true if a username already exists.
The class is annotated with the @Service annotation so we can inject it into other Spring managed beans.

public class UserService {

  private final Map<String, User> db;

  public UserService() {
    this.db = new ConcurrentHashMap<>();

  public User lookup(String username) {
    return this.db.get(username);

  public void save(User user) {
    this.db.put(user.getUsername(), user);

  public boolean usernameExists(String username) {
    return this.db.containsKey(username);



In this section we are configuring Spring Security that is responsible for securing the back end. I took inspiration for the configuration from the JHipster project and copied the three classes JWTConfigurer, JWTFilter and TokenProvider from this project.

To encrypt the password we configure a PasswordEncoder bean.

  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12);


Then we need to create an implementation of the UserDetailsService interface. Because of the interface, we have to implement the method loadUserByUsername.

public class AppUserDetailService implements UserDetailsService {

  private final UserService userService;

  public AppUserDetailService(UserService userService) {
    this.userService = userService;

  public final UserDetails loadUserByUsername(String username)
      throws UsernameNotFoundException {
    final User user = this.userService.lookup(username);
    if (user == null) {
      throw new UsernameNotFoundException("User '" + username + "' not found");

    return org.springframework.security.core.userdetails.User.withUsername(username)



This implementation fetches the User object with the UserService from the "database" and instantiates an UserDetails. The code utilizes the builder from the User class to create the UserDetails instance.

Next we look at the TokenProvider class. This class provides a method to create a JWT (createToken) and a method to parse and validate a JWT and to return an Authentication object (getAuthentication).

  public String createToken(String username) {
    Date now = new Date();
    Date validity = new Date(now.getTime() + this.tokenValidityInMilliseconds);

    return Jwts.builder().setId(UUID.randomUUID().toString()).setSubject(username)
        .setIssuedAt(now).signWith(SignatureAlgorithm.HS512, this.secretKey)


The JWT is created with the Jwts builder from the jjwt library. For the signature, the builder needs to know the algorithm (HMAC with SHA-512) and it needs a secret key. The application reads this key from the application.yml file (the key has to be a base64 encoded string). The method setExpiration sets the date until when the token is valid. The current date and time is set with the setIssuedAt method and the username is put into the subject with setSubject. The final call to compact() builds and returns the JWT as a String.

  public Authentication getAuthentication(String token) {
    String username = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token)
    UserDetails userDetails = this.userService.loadUserByUsername(username);

    return new UsernamePasswordAuthenticationToken(userDetails, "",


This method receives the JWT and parses it. The code needs to set the same secret key as in the token creation method to check the validity of the signature. After parse succeeds the username is extracted and the code loads the UserDetails from the "database". It's debatable if the application should do a database query here, because we can store all the authorities (roles) of a user in the JWT and then extract it here. Depending on the use case and how long the JWT is valid, checking the user in the database could make sense. The advantage is that we can block users and change their roles immediately.

Next part of our security configuration is the JWTFilter class. This class is injected into the Spring Security filter chain and every HTTP request, that needs to be authenticated, flows through this filter.

  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
      FilterChain filterChain) throws IOException, ServletException {
    try {
      HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
      String jwt = resolveToken(httpServletRequest);
      if (jwt != null) {
        Authentication authentication = this.tokenProvider.getAuthentication(jwt);
        if (authentication != null) {
      filterChain.doFilter(servletRequest, servletResponse);
    catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException
        | SignatureException | UsernameNotFoundException e) {
      Application.logger.info("Security exception {}", e.getMessage());
      ((HttpServletResponse) servletResponse)


The client sends the token in the Authorization header

Authorization:Bearer eyJhbGciOiJIUzUxMiJ9......

The first thing the filter does is extracting the JWT from this header (resolveToken). Then it calls the getAuthentication method from the TokenProvider. This method returns either an Authentication object or null when the token is valid but the user does not exist or it throws one of several exceptions when the validation of the JWT failed. In all error cases the filter returns a HTTP status code of 401.

Next class is the JWTConfigurer. This is a helper class that adds the JWTFilter into the chain of security filters. It inserts the JWTFilter before the UsernamePasswordAuthenticationFilter filter, to check if the request contains a valid JWT before presenting a login dialog.

public class JWTConfigurer
    extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

  private final TokenProvider tokenProvider;

  public JWTConfigurer(TokenProvider tokenProvider) {
    this.tokenProvider = tokenProvider;

  public void configure(HttpSecurity http) throws Exception {
    JWTFilter customFilter = new JWTFilter(this.tokenProvider);
    http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);


Finally, we need to configure what endpoints are secured and which ones are public. We do that with a configuration class that extends WebSecurityConfigurerAdapter subclass.

public class SecurityConfig extends WebSecurityConfigurerAdapter {

  private final TokenProvider tokenProvider;

  public SecurityConfig(TokenProvider tokenProvider) {
    this.tokenProvider = tokenProvider;

  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();

  protected void configure(HttpSecurity http) throws Exception {
    // @formatter:off
    //.httpBasic() // optional, if you want to access
    //  .and()     // the services from a browser
      .apply(new JWTConfigurer(this.tokenProvider));
    // @formatter:on



The configuration disables CSRF protection and enables CORS handling. Then it sets the session creation policy to STATELESS that prevents Spring Security from creating HttpSession objects and cookies. Each request already contains the Authorization header with the JWT and we don't need additional session cookies. The HTTP endpoints /signup, /login and /public are accessible without an authentication. All the other endpoints are secured and require a valid JWT token. At the end, the JWTConfigurer helper class injects the JWTFilter into the Spring Security filter chain.


The configuration for the security is now finalized and we can move on to the implementation of the HTTP endpoints. We start with the method that handles the signup request.

  public String signup(@RequestBody User signupUser) {
    if (this.userService.usernameExists(signupUser.getUsername())) {
      return "EXISTS";

    return this.tokenProvider.createToken(signupUser.getUsername());


This method checks if the username already exists and returns the string "EXISTS" in that case. The client then presents an error message when it receives this response.

If the username does not already exist the password is encoded with bcrypt before it's stored in the "database". Then the method creates the JWT and returns it.

Next method is the handler for the /login request. This for the case when the user already has an account in our application but the JWT has expired or the user logs in from a new device where the JWT is not stored locally.

  public String authorize(@Valid @RequestBody User loginUser,
      HttpServletResponse response) {
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
        loginUser.getUsername(), loginUser.getPassword());

    try {
      return this.tokenProvider.createToken(loginUser.getUsername());
    catch (AuthenticationException e) {
      Application.logger.info("Security exception {}", e.getMessage());
      return null;


This method creates an instance of UsernamePasswordAuthenticationToken and calls authenticate of the authenticationManager. When username and/or password are not valid this call throws an exception and the method returns the status code 401. When the authentication is successful the method creates a JWT and returns it to the client.

And finally we create a test endpoint that is only accessible with a valid JWT.

package ch.rasc.jwt;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

public class TestController {

  public String publicService() {
    return "This message is public";

  public String secretService() {
    return "A secret message";



The client shows the response from this method on the home page. This concludes the server part of our application.


The client is built with the Ionic framework and based on the blank starter template.

ionic start jwt blank

Next we install the @auth0/angular-jwt library that simplifies the JWT handling and ng2-validation for additional form validation functions.

npm install @auth0/angular-jwt
npm install ng2-validation

JSON Web Tokens are usually sent in the HTTP header and we could write code ourselves that reads the token from storage and puts it into the HTTP header, but @auth0/angular-jwt handles this for us and automatically attaches the JWT as an Authorization header every time the app is doing a HTTP request. The library does that with a HttpInterceptor that it attaches to Angular's HTTP client service.

@auth0/angular-jwt also introduces a few helper methods for checking the expiration date of a token and for decoding the token's payload.


The start command already created a page called HomePage. The app presents this page to the user if he has a valid JWT. We also need to create two additional pages, a service and a route guard.

ng generate page Signup
ng generate page Login
ng generate service Auth
ng generate guard Auth

App Configuration

Next we need to edit app.module.ts. We have to add our components and configure the @auth0/angular-jwt library.

export function tokenGetter() {
  return localStorage.getItem('jwt_token');

  declarations: [AppComponent, HomePage, LoginPage, SignupPage],
  entryComponents: [],
  imports: [BrowserModule,
      config: {
        tokenGetter: tokenGetter,
        whitelistedDomains: environment.whitelistedDomains
    RouterModule.forRoot(routes, {useHash: true})],
  providers: [
    {provide: RouteReuseStrategy, useClass: IonicRouteStrategy}
  bootstrap: [AppComponent]
export class AppModule {


To import the angular-jwt library we add JwtModule to the imports section. By default, the @auth0/angular-jwt library reads the token from localStorage with the key id_token. In this example we use a custom token getter method, that reads the token from localStorage with the name jwt_token. You have to export this getter function, otherwise Angular's ngc compiler throws an error.

Because @auth0/angular-jwt attaches a HttpInterceptor to Angular's HTTP client service, every request initiated from our application goes through that interceptor. By default, the library does not add the Authorization header to any request. You have to whitelist URLs where the library is allowed to add the Authorization header with the whitelistedDomains property.

In this application, only requests sent to localhost:8080 contain the Authorization header.


The AuthService class manages the authentication of our app. It checks the JWT, sends login and signup requests to the server and handles the responses.

Our app utilizes the ReplaySubject provided by the RxJs library to notify other parts of the app when the authorization state changes. This object represents an observable sequence as well as an observer. Every time the app calls next on this object all subscribers are notified.
We only expose the observable part of the subject to others, so they can only subscribe but not call next.

  providedIn: 'root'
export class AuthService {

  private readonly jwtTokenName = 'jwt_token';

  private authUser = new ReplaySubject<any>(1);
  public authUserObservable = this.authUser.asObservable();


Every time the application starts up it calls the hasAccess() method. This function checks if a JWT is stored locally.

  hasAccess(): Promise<boolean> {
    const jwt = localStorage.getItem(this.jwtTokenName);

    if (jwt && !this.jwtHelper.isTokenExpired(jwt)) {

      return new Promise((resolve, _) => {

          .subscribe(() => {
            err => {

      // OR
      // this.authUser.next(jwt);
      // Promise.resolve(true);
    } else {
      return Promise.resolve(false);


The function fetches the JWT from localStorage, if it exists it checks the validity and then it calls the secure endpoint /authenticate. If that call succeeds it calls authUser.next with the JWT as parameter and returns true. This method is called from the AuthGuard, which we discuss in the next section, and guards expect a true or false return value.

If the /authenticate call fails or there is no JWT locally stored or the token is expired, the app deletes the token, calls authUser.next(null) and navigates to the login page

  logout() {
    this.navCtrl.navigateRoot('login', {replaceUrl: true});


The logout method is also called when the user clicks on the logout icon.

The call to /authenticate is again debatable, like the query to fetch the user from the database. It depends on the use case. If the client and the server only depend on the information stored in the JWT without ever check the server and the user database, the application would not be able to immediately block a user or change the roles of a user. This is not a problem when the JWT has a very short validity, but in this example the token is valid for 30 days.

Next method we implement in the AuthService class is login. This method is called from the login page after the user enters his username and password and taps the login button. The method posts the data to the /login endpoint and receives back a JWT when the login information was correct. The code stores the token in localStorage and then calls authUser.next.

  login(values: any): Observable<string> {
    return this.httpClient.post(`${environment.serverURL}/login`, values, {responseType: 'text'})
      .pipe(tap(jwt => this.handleJwtResponse(jwt)));


  private handleJwtResponse(jwt: string): string {
    localStorage.setItem(this.jwtTokenName, jwt);

    return jwt;


And finally we implement the signup method which is called from the signup page. The function posts the sign up information to the /signup endpoint and receives back either a JWT or the string EXISTS when the username already exists. When the response is a JWT it calls handleJwtResponse which stores the token.

  signup(values: any): Observable<string> {
    return this.httpClient.post(`${environment.serverURL}/signup`, values, {responseType: 'text'})
      .pipe(tap(jwt => {
        if (jwt !== 'EXISTS') {
          return this.handleJwtResponse(jwt);
        return jwt;



In the route configuration you see that the application protects the route to the home page with a guard

const routes: Routes = [
  {path: '', redirectTo: 'home', pathMatch: 'full'},
  {path: 'home', component: HomePage, canActivate: [AuthGuard]},
  {path: 'login', component: LoginPage},
  {path: 'signup', component: SignupPage},
  {path: '**', redirectTo: '/home'}


This guard calls the hasAccess method from the AuthService class

import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree} from '@angular/router';
import {Observable} from 'rxjs';
import {AuthService} from './auth.service';

  providedIn: 'root'
export class AuthGuard implements CanActivate {

  constructor(private readonly authService: AuthService) {

    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.authService.hasAccess();



This method either returns true or false. False blocks the route request and displays the login page. True allows the request and in this case shows the home page to the user.

Login Page

The login page is a simple form implemented with the template driven approach and presents the user a username and password field and two buttons one to login and the other to switch to the signup page.

When the user taps on the login button, the app opens a loading dialog and calls the method login from AuthService. If an error occurs a toast message is presented to tell the user that something went wrong. When the login succeeds the user sees the home page.

Template: login.page.html
TypeScript class: login.page.ts

Sign up Page

The signup page display the input fields name, email, username and password. A tap on the signup button calls the authService.signup function.

To handle the special "EXISTS" case the app injects the NgModel of the username field into the SignupPage class. And later when the server returns the string "EXISTS" the app can access the FormControl and set an error.

Exists error

Template: signup.page.html
TypeScript class: signup.page.ts

Home Page

The HomePage is the page the users sees when the login and sign up was successful or when the app finds a valid JWT in the storage. The home page displays the username of the logged in user and the response of the /secret HTTP endpoint

The HomePage subscribes to the authService.authUserObservable in the constructor and when it receives a JWT it decodes it with the JwtHelper.decodeToken function, extracts the username from the subject field (sub) and assigns it to the instance variable user.

    this.authService.authUserObservable.subscribe(jwt => {
      if (jwt) {
        const decoded = jwtHelper.decodeToken(jwt);
        this.user = decoded.sub;
      } else {
        this.user = null;


The ngOnInit lifecycle method calls the /secret endpoint and assigns the response to the message instance variable.

  ngOnInit() {
    this.httpClient.get(`${environment.serverURL}/secret`, {responseType: 'text'}).subscribe(
      text => this.message = text,
      err => console.log(err)


The HomePage also displays a logout icon in the top right corner. A tap on this icon calls the authService.logout() method.

You find the complete source code for this project on GitHub.