Reputation: 31
I am working on a spring boot + angular project in which a user logs in from Angular front end to the authentication api on Spring Boot, which returns a JWT token. I also set up an interceptor on Angular that appends the Authorization header with the JWT token for all requests.
I am looking for a way to intercept angualar requests so that when spring boot throws a 401 error once the JWT token is expired, the Angular front end will try to contact the new refreshtoken endpoint with the expired JWT and a new "isRefreshToken" header set to true to receive a new JWT.
This is my current AuthService
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private http: HttpClient) {}
login(username: string, password: string) {
return this.http
.post<iUser>('http://localhost:8080/authenticate', { username, password }).pipe(
tap(res => this.setSession(res)),
shareReplay()
)
}
refreshToken(){
return this.http.post<iUser>('http://localhost:8080/refreshtoken', {responseType: 'text' as 'json'}).pipe(
tap(res => this.setSession(res)),
shareReplay()
)
}
private setSession(authResult) {
let tokenInfo = this.getDecodedAccessToken(authResult.token);
const expiresAt = moment(tokenInfo.exp);
localStorage.setItem('id_token', authResult.token);
localStorage.setItem('expires_at', JSON.stringify(expiresAt.valueOf()));
localStorage.setItem('userId', tokenInfo.userId);
}
logout() {
localStorage.removeItem('id_token');
localStorage.removeItem('expires_at');
localStorage.removeItem('userId');
}
public isLoggedIn() {
return moment().isBefore(this.getExpiration());
}
isLoggedOut() {
return !this.isLoggedIn();
}
getExpiration() {
const expiration = localStorage.getItem('expires_at');
const expiresAt = JSON.parse(expiration);
return moment.unix(expiresAt);
}
getDecodedAccessToken(token: string): any {
try{
return jwt_decode(token);
}
catch(Error){
return null;
}
}
}
While this is the interceptor I am using:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(private router: Router, private authService: AuthService){}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
let url = req.url.includes('localhost');
const idToken = localStorage.getItem('id_token');
if (idToken && url) {
const cloned = req.clone({
headers: req.headers.set('Authorization', 'Bearer ' + idToken),
});
console.log(cloned);
return next.handle(cloned);
} else {
return next.handle(req);
}
}
}
Upvotes: 1
Views: 2907
Reputation: 1068
I can suggest to you another way of doing this which I used recently to log out a user when the token expires. Let me share my approach first:
loginUser(email: string, password: string) {
const authData: AuthData = { email: email, password: password };
this.http.post<{ token: string, expiresIn: number }>('http://localhost:3000/api/users/login', authData).subscribe( response => {
const token = response.token;
this.token = token;
if(token) {
const expiresInDuration = response.expiresIn;
this.tokenTimer = setTimeout(() => {
this.logout();
}, expiresInDuration*1000);
this.isAuthenticated = true;
this.authStatusListener.next(true);
const now = new Date();
const expirationDate = new Date(now.getTime() + (expiresInDuration * 1000));
this.saveAuthData(token, expirationDate);
this.router.navigate(['']);
}
});
}
logout() {
this.token = null;
this.isAuthenticated = false;
this.authStatusListener.next(false);
clearTimeout(this.tokenTimer);
this.clearAuthData();
this.router.navigate(['']);
}
private saveAuthData(token: string, expirationDate: Date) {
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate.toISOString());
}
So what I have done here is I have received a expireIn
value which is in seconds when the token will expire. Then I have set a timeout
callback method which will be called when that time is reached. Here I've logged out in your case you can call the API for the refresh token & make desired changes as per your requirement. So coming back to the point, I have set the expiration date & time with respect to the current date-time of login with expiresIn
duration. Also added a authStatusListener
Subject
which will listen to the state of the authentication till it expires.
Nothing extra to do in Interceptor/Guard for token expiration
For controlling the logout button in header just do like this:
userIsAuthenticated = false;
private authListenerSubs: Subscription;
constructor(private authService: AuthService) { }
onLogout() {
this.authService.logout();
}
ngOnInit() {
this.authListenerSubs = this.authService.getAuthStatusListener().subscribe(isAuthenticated => {
this.userIsAuthenticated = isAuthenticated;
});
}
ngOnDestroy() {
this.authListenerSubs.unsubscribe();
}
Use userIsAuthenticated
in ngIf
in the HTML.
For a real scenario you can take help of this github repo.
Upvotes: 0