Skip to content

A New TwoFactorAuthenticationFilter #9534

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bensiegler opened this issue Apr 1, 2021 · 1 comment
Closed

A New TwoFactorAuthenticationFilter #9534

bensiegler opened this issue Apr 1, 2021 · 1 comment
Assignees
Labels
in: web An issue in web modules (web, webmvc) status: duplicate A duplicate of another issue type: enhancement A general enhancement

Comments

@bensiegler
Copy link

bensiegler commented Apr 1, 2021

Hi,

I want to make it easier to implement two factor authentication (OTPs) using Spring Security. I found myself struggling to do it in my project and had to create a kind of janky workaround since there were a number of final methods I could not override.

That gave me the idea to actually go and make some real changes and came up with the following potential structure:

The key here is a new TwoFactorAuthenticationFilter which extends AbstractAuthenticationProcessingFilter. This filter would be responsible for intercepting POST requests to a login processing URL (with username and password) in addition to POST requests to a 2FA processing URL. This class would by default use a ProviderManager with at least two providers. One to confirm username and password and the other to confirm 2FA codes.

When a POST to the login processing URL is received, the usual username and password authentication process is followed:

  • Create unauthenticated UsernamePasswordAuthenticationToken
  • Pass to AuthenticationManager for authentication.

If authentication succeeds the filter has to decide whether to initiate the 2FA process for the user, or return the authenticated
UsernamePasswordAuthenticationToken and lets the user proceed. This decision will be made by requesting information from the principal of type TwoFactorUserDetails. This class is a subclass of UserDetails and simply adds a method to retrieve 2FA
status.

If the isTwoFactorAuthenticationEnabled() comes back true, the 2FA process begins. This starts with delegating to a
TwoFactorCodeService interface. TwoFactorCodeServices implement three abstract methods:

  1. generateCode()
  2. validateCode()
  3. cleanUp()

These three methods will be implemented in a DefaultTwoFactorCodeService and they will make use of:

  • A TwoFactorCodeRepository interface. Two concrete subclasses will be provided, a DatabaseTwoFactorCodeRepository and a InMemoryTwoFactorCodeRepository.
  • A TwoFactorCodeGenerationStrategy interface. With a single SixDigitGenerationStrategy concrete subclass.

The TwoFactorAuthenticationFilter will use generateCode() to get its hands on a TwoFactorAuthenticationCodeWrapper
which wraps a TwoFactorAuthenticationCode with the sessionId, the username, and the time the code was created (this information will be stored in the TwoFactorCodeRepository as well).

Once the code has been generated, the filter will delegate to a TwoFactorCodeSendStrategyDelegator interface with one sendCode() abstract method this delegator will then be tasked with calling a TwoFactorCodeStrategy interface sendCode() method.

If there is an exception thrown while sending the code, the filter will invoke a CodeSendFailureStrategy to handleFailure().
The default for this is NullCodeSendFailureStrategy. The filter then proceeds by redirecting the user to the twoFactorRedirectUrl and returning null so that the doFilter() method in the AbstractAuthenticationProcessingFilter calls return;. It is then the users' responsibility to create and a 2FA page and the form match the POST to twoFactorAuthenticationProcessingUrl assigned to the filter.

When the request with the code is then received at for example POST /2FA/authenticate, the filter is then again invoked. This time it creates a TwoFactorAuthenticationToken which will be passed to the ProviderManager. The ProviderManager will then call the TwoFactorAuthenticationProvider which will use the TwoFactorAuthenticationService to validate the received code. If the code is correct and not expired, cleanUp() will be called and new and authenticated TwoFactorAuthenticationToken will be returned to the filter which returns it to the super doFilter() method.

This is rough idea of how I think this could work there are a few gaps in it. Such as redirects if not awaiting a code and code re-sending options etc. But i'm wondering what people think.

@bensiegler bensiegler added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Apr 1, 2021
@jzheaux
Copy link
Contributor

jzheaux commented Feb 10, 2022

@bensiegler, thanks for the suggestion and the detailed write-up.

It looks like you've reached out to @rwinch on #2603 already, so I'll close this ticket as a duplicate so the conversation can proceed in one place.

@jzheaux jzheaux closed this as completed Feb 10, 2022
@jzheaux jzheaux self-assigned this Feb 10, 2022
@jzheaux jzheaux added in: web An issue in web modules (web, webmvc) status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web An issue in web modules (web, webmvc) status: duplicate A duplicate of another issue type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants