DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Authentication With Remote LDAP Server in Spring Web MVC
  • Leveraging Salesforce Using a Client Written In Angular
  • How to Implement Two-Factor Authentication in a Spring Boot OAuth Server? Part 1: Configuration
  • Bing Maps With Angular in a Spring Boot Application

Trending

  • Recurrent Workflows With Cloud Native Dapr Jobs
  • My LLM Journey as a Software Engineer Exploring a New Domain
  • While Performing Dependency Selection, I Avoid the Loss Of Sleep From Node.js Libraries' Dangers
  • Solid Testing Strategies for Salesforce Releases
  1. DZone
  2. Coding
  3. Frameworks
  4. Angular JWT Autorefresh With Spring Boot

Angular JWT Autorefresh With Spring Boot

By 
Sven Loesekann user avatar
Sven Loesekann
·
Apr. 27, 20 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
15.4K Views

Join the DZone community and get the full member experience.

Join For Free

If JWTs are used for security, there are use cases for using short-lived tokens — for example, if you use the token in a URL that gets opened. To make that possible, the Angular frontend needs to refresh the token before it expires automatically. To make that possible, Angular and RxJs features are used in the frontend and a Spring Boot REST endpoint checks and updates the JWT in the backend. 

The frontend design is based on the concepts explained in this article. It is a valuable resource about caching with RxJs.

In this article, the AngularPortfolioMgr project is used to provide examples. The project is currently a work in progress, but the feature shown in this article is done.

The Angular Frontend

The AngularPortfolioMgr project has a signin/login process that receives the JWT and starts the autoupdates of the tokens. The login.component.ts gets the token.service.ts injected and sets the token in the login method in the service: 

TypeScript
 




xxxxxxxxxx
1
11


 
1
private login(login: Login): void {
2
        if (login && login.token && login.id) {
3
            this.tokenService.token = login.token;
4
            this.tokenService.userId = login.id;
5
            this.data.login = login;
6
            this.loginFailed = false;
7
            this.dialogRef.close(this.data.login);
8
        } else {
9
            this.loginFailed = true;
10
        }
11
    }



On line 1, we check that the login was successful. On line 2, the token is set in the TokenService setter that starts the autoupdate of the tokens. On line 3, the userId is set in the TokenService setter. On line 7, the login dialog gets closed.

TokenService

The token.service.ts is used to manage and refresh the tokens received in the login process. It requests a new token every 45 seconds and provides with a getter/setter and an Observable for the token. The Subscriber of the Observable has to take care to unsubscribe properly. The Service is set up like this:

TypeScript
 




xxxxxxxxxx
1
14


 
1
@Injectable({
2
  providedIn: 'root'
3
})
4
export class TokenService {
5
  private myTokenCache: Observable<RefreshTokenResponse>;   
6
  private readonly CACHE_SIZE = 1;
7
  private readonly REFRESH_INTERVAL = 45000; //45 sec
8
  private myToken: string;
9
  private myUserId: number;
10
  private myTokenSubscription: Subscription;
11
  private stopTimer: Subject<boolean>;
12

          
13
  constructor(private http: HttpClient, private router: Router) { }
14

          



In lines 1-3, is it created in 'root' to have one the application.

In line 5, the Observable to cache the RefreshTokenResponse interfaces with the token is defined.

In line 6, the cache size is set to 1 because we only need 1 token.

In line 7, the refresh interval is set to 45 seconds.

In line 8, the current value of the token gets defined.

In line 10, the subscription to the to the cache gets defined. 

In line 11, the stopTimer Subject gets defined to stop the refresh pipe.

In line 12, the HttpClient and the Router gets injected by Angular to request the token updates and routes the user to the login if the updates fail.

The token setter sets the initial token value and starts the autorefesh of the token: 

TypeScript
 




xxxxxxxxxx
1
14


 
1
  set token(token: string) {
2
    this.myToken = token;
3
    if(token && !this.myTokenCache) {
4
        const myStopTimer = new Subject<boolean>();
5
        this.stopTimer = myStopTimer;
6
        const myTimer = timer(0, this.REFRESH_INTERVAL);
7
        this.myTokenCache = myTimer.pipe(
8
            takeUntil(myStopTimer),
9
            switchMap(() => this.refreshToken()),
10
            shareReplay(this.CACHE_SIZE));
11
        this.myTokenSubscription = this.myTokenCache.subscribe(newToken =>  
12
            this.myToken = newToken.refreshToken, () => this.clear());
13
    }
14
  }



In line 2, the new token is set in the service property. In line 3, it is checked if the tokenCache needs to be initialized.

In line 4-5, the myStopTime Subject gets created to stop the pipe when the Subject emits, and it is set to the property. In line 6, the timer is set up to tick every 45 seconds.

In line 7, the timer is connected to a pipe and the token cache Oberservable. In line 8, the takeUntil stops the pipe when the myStopTimer emits something.

In line 9, switchMap gets used to update the token cache. In line 10, shareReplay is used to to provide a cache for all the subscribers of the token cache Observable.

In line 11, the token Subscription gets set up and set to be able to unsubscribe. In line 12, the myToken property gets updated with the subscription and in the error case the clear() method gets called. 

The refreshToken method requests a new token from the backend.

TypeScript
 




xxxxxxxxxx
1


 
1
  private refreshToken(): Observable<RefreshTokenResponse> {    
2
    return this.http.get<RefreshTokenResponse>('/rest/auth/refreshToken', {
3
        headers: this.createTokenHeader()
4
    });
5
  }



This is a normal Angular service call where the header parameters need to be set.

The createTokenHeader method creates the needed headers for the TokenService and the token.interceptor.ts. 

TypeScript
 




xxxxxxxxxx
1


 
1
public createTokenHeader(): HttpHeaders {
2
  let reqOptions = new HttpHeaders().set('Content-Type','application/json')
3
  if(this.token) {
4
     reqOptions = new HttpHeaders().set( 'Content-Type', 'application/json'         ).set('Authorization', `Bearer ${this.token}`);
5
  }
6
  return reqOptions;
7
}



The createTokenHeader method adds the 'Authorization' header param if the token is availiable. It is also used in the token.interceptor.ts to add the header to all other service requests if the user is logged in.

The clear method cleans up the service after a failed token refresh request or a user logout.

TypeScript
 




xxxxxxxxxx
1
13


 
1
  public clear() {
2
    if(this.myTokenSubscription) {
3
        this.myTokenSubscription.unsubscribe();     
4
    }
5
    if(this.stopTimer) {        
6
        this.stopTimer.next(true);
7
        this.stopTimer = null;
8
    }
9
    this.myTokenCache = null;
10
    this.myToken = null;
11
    this.myUserId = null;
12
    this.router.navigate(['/login']);
13
  }



In lines 2-4, the subscription that refreshes the local token value is unsubscribed. In lines 5-8, the stopTimes emits a value to stop the refresh pipe and the property is reset.

In lines 9-11, the service properties are reset. That includes the timer(myTokenCache). In line 12, the user gets routed to the login component. The main.route.guard.ts will prevent routing to other routes by checking for the token in the TokenService.

The Spring Boot Backend

The backend provides a REST endpoint to check/update the token send. It uses a Spring Reactor, but the code can be easily used in any Spring application.

Java
 




xxxxxxxxxx
1
13


 
1
@RestController
2
@RequestMapping("/rest/auth")
3
public class AuthenticationController {
4
  ...
5
  @Autowired
6
  private AppUserService appUserService;
7
  ... 
8
  @GetMapping("/refreshToken")
9
  public Mono<RefreshTokenDto> getRefreshToken(@RequestHeader(value = 
10
        HttpHeaders.AUTHORIZATION) String bearerStr) {
11
        return this.appUserService.refreshToken(bearerStr);
12
    }
13
}



In lines 1-3, a Spring REST controller AuthenticationController.java is defined with the base mapping to '/rest/auth'.

In lines 5-6, the AppUserService gets injected.

In lines 8-11, the rest endpoint '/refreshToken' gets defined and the header paramter of the Authorization is injected in the bearerStr variable. Then the refreshToken method gets called and the result gets returned in the RefreshTokenDto.java.

In the AppUserService.java, the token string gets checked and updated.

Java
 




xxxxxxxxxx
1


 
1
public Mono<RefreshTokenDto> refreshToken(String bearerToken) {
2
  Optional<String> tokenOpt = this.jwtTokenProvider.resolveToken(bearerToken);
3
    if(tokenOpt.isEmpty()) {
4
      throw new AuthorizationServiceException("Invalid token");
5
    }
6
    String newToken = this.jwtTokenProvider.refreshToken(tokenOpt.get());
7
    LOGGER.info("Jwt Token refreshed.");
8
    return Mono.just(new RefreshTokenDto(newToken));
9
}



In lines 2-5, the bearer part of the token is stripped off. If not, an exception is throw. In line 6, the JwtTokenProvider.java is used to check/refresh the token. 

In line 8, the new token String is put in the RefreshTokenDto.java and returned.

In the JwtTokenProvider.java, the token string gets checked, updated, and signed.

Java
 




x
24


 
1
public String refreshToken(String token) {
2
    validateToken(token);
3
    Optional<Jws<Claims>> claimsOpt = this.getClaims(Optional.of(token));
4
    if(claimsOpt.isEmpty()) {
5
        throw new AuthorizationServiceException("Invalid token claims");
6
    }
7
    Claims claims = claimsOpt.get().getBody();
8
    claims.setIssuedAt(new Date());
9
    claims.setExpiration(new Date(Instant.now().toEpochMilli() +
10
                                  validityInMilliseconds));
11
    String newToken = 
12
    Jwts.builder().setClaims(claims).signWith(this.jwtTokenKey,                                                         SignatureAlgorithm.HS256).compact();
13
    return newToken;
14
}
15

          
16
public boolean validateToken(String token) {
17
    try {           
18
        Jwts.parserBuilder().setSigningKey(this.jwtTokenKey)
19
            .build().parseClaimsJws(token);
20
        return true;
21
    } catch (JwtException | IllegalArgumentException e) {
22
        throw new AuthenticationException("Expired or invalid JWT token",e);
23
    }
24
}



In line 2, the validateToken method is called. It checks that the token is a token, the signature is correct, and the token is not expired.

In lines 3-6, the claims of the token are retrieved and checked. In lines 7-10, the claims are updated to the new times. In lines 11-13, a new token string is created and signed.

Conclusion

Adding the auto-refresh token feature to the Angular frontend required surprisingly little code. The capabilities of Angular and RxJs were a great help. To have community resources like this article saves a lot of time. Angular, with its community, is a great opportunity for anybody with a Java background.

Spring Boot makes creating the backed easy and provides the opportunity to use reactive programming from frontend to the backend.

Spring Framework Spring Boot AngularJS JWT (JSON Web Token)

Opinions expressed by DZone contributors are their own.

Related

  • Authentication With Remote LDAP Server in Spring Web MVC
  • Leveraging Salesforce Using a Client Written In Angular
  • How to Implement Two-Factor Authentication in a Spring Boot OAuth Server? Part 1: Configuration
  • Bing Maps With Angular in a Spring Boot Application

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!