Consider a `OneTimeToken` integration with Spring MVC
This would simplify the resolution of an OneTimeToken. Currently, a OneTimeTokenService should be injected and a OneTimeTokenAuthenticationRequest must be created manually.
@GetMapping("/ott/generate")
public String generateOtt(Authentication authentication, Model model) {
OneTimeTokenAuthenticationRequest request = new OneTimeTokenAuthenticationRequest(authentication.getName());
OneTimeToken oneTimeToken = this.oneTimeTokenService.generate(request);
model.addAttribute("oneTimeToken", oneTimeToken);
return "ott-generate";
}
For example, authenticated users might want to generate a One-Time Token to log in from another device, let's say a TV, where it is very unlikely that they want to type their long passwords. This is also great to avoid typing your password into a device that might be public, like a hotel TV or a cybercafe computer.
One idea is to provide a HandlerMethodArgumentResolver that resolves the OneTimeToken parameter.
class OneTimeTokenRequestArgumentResolver implements HandlerMethodArgumentResolver {
private final SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
@Override
public boolean supportsParameter(MethodParameter parameter) {
return OneTimeToken.class.equals(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
OneTimeTokenService oneTimeTokenService = this.applicationContext.getBean(OneTimeTokenService.class);
}
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
if (this.authenticationTrustResolver.isAnonymous(authentication)) {
return null;
}
OneTimeTokenAuthenticationRequest request = new OneTimeTokenAuthenticationRequest(authentication.getName());
return oneTimeTokenService.generate(request);
}
}
@GetMapping("/ott/generate")
public String generateOtt(OneTimeToken oneTimeToken, Model model) {
model.addAttribute("oneTimeToken", oneTimeToken);
return "ott-generate";
}
We could also provide an additional annotation to allow customizing which OneTimeTokenService to use if the application has more than one.
@GetMapping("/ott/generate")
public String generateOtt(@GenerateOneTimeToken(oneTimeTokenServiceBeanName = "deviceOneTimeTokenService") OneTimeToken oneTimeToken, Model model) {
model.addAttribute("oneTimeToken", oneTimeToken);
return "ott-generate";
}
An additional idea is to provide a HandlerMethodArgumentResolver that could detect OneTimeToken parameters annotated with @ConsumedOneTimeToken, it would:
- Use an
AuthenticationConverterto create aOneTimeTokenAuthenticationToken - Pass the
OneTimeTokenAuthenticationTokento theOneTimeTokenService#consume - Resolve the consumed
OneTimeTokenas the method argument
Then users would have total control over what to do with the consumed OneTimeToken without the whole burden of consuming it manually.
This idea came from a scenario where I don't want to authenticate the current session, instead, I'd like the session that originated the OTT authentication request. Currently, one has to configure several components to make sure that the OTT contains the session id and the session gets authenticated. While it's too much configuration it does not feel optimal as well.
Related https://github.com/spring-projects/spring-security/pull/15492#discussion_r1729166058
Hi @marcusdacoregio. Can I take this to work?