I want to add multi-factor authentication with TOTP soft tokens to an Angular & Spring application, while keeping everything as close as possible to the defaults of Spring Boot Security Starter.
The token-validation happens locally (with the aerogear-otp-java library), no third party API provider.
Setting up tokens for a user works, but validating them by leveraging Spring Security Authentication Manager/Providers does not.
TL;DR
- What is the official way to integrate an additional AuthenticationProvider into a Spring Boot Security Starter configured system?
- What are recommended ways to prevent replay attacks?
Long Version
The API has an endpoint /auth/token
from which the frontend can get a JWT token by providing username and password. The response also includes an authentication-status, which can be either AUTHENTICATED or PRE_AUTHENTICATED_MFA_REQUIRED.
If the user requires MFA, the token is issued with a single granted authority of PRE_AUTHENTICATED_MFA_REQUIRED
and an expiration-time of 5 minutes. This allows the user to access the endpoint /auth/mfa-token
where they can provide the TOTP code from their Authenticator app and get the fully authenticated token to access the site.
Provider and Token
I have created my custom MfaAuthenticationProvider
which implements AuthenticationProvider
:
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// validate the OTP code
}
@Override
public boolean supports(Class<?> authentication) {
return OneTimePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
And a OneTimePasswordAuthenticationToken
which extends AbstractAuthenticationToken
to hold the username (taken from the signed JWT) and the OTP code.
Config
I have my custom WebSecurityConfigurerAdapter
, where I add my custom AuthenticationProvider
via http.authenticationProvider()
. Accoring to the JavaDoc, this seems to be the right place:
Allows adding an additional AuthenticationProvider to be used
The relevant parts of my SecurityConfig
looks like this.
@Configuration
@EnableWebSecurity
@EnableJpaAuditing(auditorAwareRef = "appSecurityAuditorAware")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TokenProvider tokenProvider;
public SecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authenticationProvider(new MfaAuthenticationProvider());
http.authorizeRequests()
// Public endpoints, HTML, Assets, Error Pages and Login
.antMatchers("/", "favicon.ico", "/asset/**", "/pages/**", "/api/auth/token").permitAll()
// MFA auth endpoint
.antMatchers("/api/auth/mfa-token").hasAuthority(ROLE_PRE_AUTH_MFA_REQUIRED)
// much more config
Controller
The AuthController
has the AuthenticationManagerBuilder
injected and is pulling it all together.
@RestController
@RequestMapping(AUTH)
public class AuthController {
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@PostMapping("/mfa-token")
public ResponseEntity<Token> mfaToken(@Valid @RequestBody OneTimePassword oneTimePassword) {
var username = SecurityUtils.getCurrentUserLogin().orElse("");
var authenticationToken = new OneTimePasswordAuthenticationToken(username, oneTimePassword.getCode());
var authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
// rest of class
However, posting against /auth/mfa-token
leads to this error:
"error": "Forbidden",
"message": "Access Denied",
"trace": "org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for de.....OneTimePasswordAuthenticationToken
Why does Spring Security not pick up my Authentication Provider? Debugging the controller shows me that DaoAuthenticationProvider
is the only Authentication Provider in AuthenticationProviderManager
.
If I expose my MfaAuthenticationProvider
as bean, it is the only Provider that is registered, so I get the opposite:
No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken.
So, how do I get both?
My Question
What is the recommended way to integrate an additional AuthenticationProvider
into a Spring Boot Security Starter configured system, so that I get both, the DaoAuthenticationProvider
and my own custom MfaAuthenticationProvider
? I want to keep the defaults of Spring Boot Scurity Starter and have my own Provider additionally.
Prevention of Replay Attack
I know that the OTP algorithm does not by itself protect against replay attacks within the time slice in which the code is valid; RFC 6238 makes this clear
The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP, which ensures one-time only use of an OTP.
I was wondering if there is a recommended way to implement protection. Since the OTP tokens are time based I am thinking of storing the last successful login on the user's model and making sure there is only one successful login per 30 seconds time slice. This of course means synchronization on the user model. Any better approaches?
Thank you.
--
PS: since this is a question about security I am looking for an answer drawing from credible and/or official sources. Thank you.