Learn Spring Security
Contents
Invalidate Token
Any web application allows the user to voluntarily logout or expires their session (not necessarily HttpSession) in case of inactivity for a long time. This can be done from SPA (Single Page Application) by just getting rid of the token stored somewhere and redirecting to the Login page. But the proper way to make this happen is to handle it with a dedicated and secured API to invalidate the token.
Invalidate token service
We will create a deleteToken() in UserService in order to specifically update the token and tokenExpiryTime to null for the authenticated user identified by username.
@Service
public class UserService {
// Other methods omitted for brevity
public void deleteToken(String username) {
AppUser appUser = this.get(username);
appUser.setToken(null);
appUser.setTokenExpiryTime(null);
appUserRepository.save(appUser);
}
}
Now we will call the above method from AuthenticationService by passing the username retrieved from the authenticated Authentication object stored in SecurityContext.
@Service
public class AuthenticationService {
// Other methods omitted for brevity
public void invalidateToken() {
userService.deleteToken(authenticationFacade.getAuthentication().getName());
}
}
Invalidate token API
Let's create a REST API which calls the above service method to invalidate the token.
@RestController
@RequestMapping("auth")
public class AuthenticationController {
// Other details omitted for brevity
@DeleteMapping("token")
public ResponseEntity invalidateToken() {
authenticationService.invalidateToken();
return ResponseEntity.noContent().build();
}
}
Secure delete token service
Similar to updateToken() service method we should not allow anyone to delete the token anonymously. We will secure the deleteToken() service method with the same pre-authorize condition as in updateToken() to ensure only the authenticated user deletes his own token. Let's define a constant variable in Authority class exclusively for authorizing the user to delete the token and annotate the deleteToken() like below:
@PreAuthorize(Authority.DELETE_TOKEN)
public void deleteToken(String username) {
// Other details omitted for brevity
}
Note
Please note we do not need to change anything in HttpSecurity, as we have configured /auth/token endpoint to be public only for POST method. So DELETE method is secured by default letting only the authenticated user to delete/invalidate his own token.
Restart the application, and generate a token from GenerateToken API with Admin user credential. Now send a DELETE request to the same endpoint with the generated token as Bearer Token. This will reset the token and tokenExpiryTime column for the Admin user to null in the APP_USER table. We can no more use the same token for any protected APIs for the Admin user.
Downside of Opaque Tokens
With so many steps to implement token-based authentication mechanism, it is quite not simpler than the out of the box Basic Auth. Though it is quite effective and efficient and addresses most of the issues of Basic Auth but it also comes along with its own limitations. And this is not because of the authentication mechanism itself, but because of the type of the token we are using so far.
- These tokens are called opaque tokens, as the name implies we can not get any information out of the random string. There is no way to verify from the token to whom it was issued to (Subject).
- Single Page Applications (SPA) receiving this token can not decide what the user is allowed to do in the user interface from the token. It has to depend on a separate endpoint (in most cases it ends with url /me or /profile) to get the user profile and their permissions. These tokens can only be used as a means of authentication and not for authorization.
- As these are opaque tokens, it should always be backed by a persistent datastore. In our case we had to associate the token with the user and it's expiry time in the database along with the AppUser record.
- API request spanning multiple microservices, where the token has to be relayed across each microservice requires a round trip to the datastore in each microservice just to validate the token.