= Multi-Factor Authentication

https://cheatsheetseries.owasp.org/cheatsheets/Multifactor_Authentication_Cheat_Sheet.html[Multi-Factor Authentication (MFA)] requires that a user provide factors in order to authenticate.
OWASP places factors into the following categories:

- Something the user knows (e.g. a password)
- Something that the user has (e.g. access to SMS or email)
- Something you are (e.g. biometrics)
- Somewhere you are (e.g. geolocation)
- Something you do (e.g. Behavior Profiling)

== `FactorGrantedAuthority`

At the time of authentication, Spring Security's authentication mechanisms add a javadoc:org.springframework.security.core.authority.FactorGrantedAuthority[].
For example, when a user authenticates using a password a `FactorGrantedAuthority` with the `authority` of `FactorGrantedAuthority.PASSWORD_AUTHORITY` is automatically added to the `Authentication`.
In order to require MFA with Spring Security you must:

- Specify an authorization rule that requires multiple factors
- Setup authentication for each of those factors

[[emfa]]
== @EnableMultiFactorAuthentication

javadoc:org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication[format=annotation] makes it easy to enable multifactor authentication.
Below you can find a configuration that adds the requirement for both passwords and OTT to every authorization rule.

include-code::./EnableMultiFactorAuthenticationConfiguration[tag=enable-mfa,indent=0]

We are now able to concisely create a configuration that always requires multiple factors.

include-code::./EnableMultiFactorAuthenticationConfiguration[tag=httpSecurity,indent=0]
<1> URLs that begin with `/admin/**` require the authorities `FACTOR_OTT`, `FACTOR_PASSWORD`, `ROLE_ADMIN`.
<2> Every other URL requires the authorities `FACTOR_OTT`, `FACTOR_PASSWORD`
<3> Set up the authentication mechanisms that can provide the required factors.

Spring Security behind the scenes knows which endpoint to go to depending on which authority is missing.
If the user logged in initially with their username and password, then Spring Security redirects to the One-Time-Token Login page.
If the user logged in initially with a token, then Spring Security redirects to the Username/Password Login page.

[[mfa-when-webauthn-registered]]
=== Conditionally Requiring MFA for WebAuthn Users

At times, you may want to conditionally require MFA only for users who have registered a WebAuthn credential (passkey).
You can achieve this by specifying javadoc:org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication#when()[when = MultiFactorCondition.WEBAUTHN_REGISTERED].

include-code::./WebAuthnConditionConfiguration[tag=enable-mfa-webauthn,indent=0]

This configuration requires `FACTOR_WEBAUTHN` and `FACTOR_PASSWORD` only when the user has registered a passkey.
It works by publishing a xref:./mfa.adoc#mfa-when-custom-conditions[`Customizer<AdditionalRequiredFactorsBuilder<Object>>`] that updates the xref:./mfa.adoc#programmatic-mfa[`withWhen`] method with the condition.

NOTE: This condition requires both a javadoc:org.springframework.security.web.webauthn.management.PublicKeyCredentialUserEntityRepository[] bean and a javadoc:org.springframework.security.web.webauthn.management.UserCredentialRepository[] bean to be published in order to determine if the user has registered a WebAuthn credential.

[[mfa-when-custom-conditions]]
=== Custom MFA Conditions

You can also publish one or more `Customizer<AdditionalRequiredFactorsBuilder<Object>>` beans to customize the factory created by `@EnableMultiFactorAuthentication`.
For example, you can conditionally apply MFA for specific users:

include-code::./CustomizerAuthorizationManagerFactoryConfiguration[tag=customizer,indent=0]

[[authorization-manager-factory]]
== AuthorizationManagerFactory

The `@EnableMultiFactorAuthentication` `authorities` property is just a shortcut for publishing an javadoc:org.springframework.security.authorization.AuthorizationManagerFactory[] Bean.
When an `AuthorizationManagerFactory` Bean is available, it is used by Spring Security to create authorization rules, like `hasAnyRole(String)`, that are defined on the `AuthorizationManagerFactory` Bean interface.
The implementation published by `@EnableMultiFactorAuthentication` will ensure that each authorization is combined with the requirement of having the specified factors.

The `AuthorizationManagerFactory` Bean below is what is published in the previously discussed xref:./mfa.adoc#emfa[`@EnableMultiFactorAuthentication` example].

include-code::./UseAuthorizationManagerFactoryConfiguration[tag=authorizationManagerFactoryBean,indent=0]


[[selective-mfa]]
== Selectively Requiring MFA

We have demonstrated how to configure an entire application to require MFA by using xref:./mfa.adoc#emfa[``@EnableMultiFactorAuthentication``s] `authorities` property.
However, there are times that an application only wants parts of the application to require MFA.
Consider the following requirements:

- URLs that begin with `/admin/` should require the authorities `FACTOR_OTT`, `FACTOR_PASSWORD`, `ROLE_ADMIN`.
- URLs that begin with `/user/settings` should require the authorities `FACTOR_OTT`, `FACTOR_PASSWORD`
- Every other URL requires an authenticated user

In this case, some URLs require MFA while others do not.
This means that the global approach that we saw before does not work.
Fortunately, we can use what we learned in xref:./mfa.adoc#authorization-manager-factory[] to solve this in a concise manner.

Start by specifying `@EnableMultiFactorAuthentication` without any authorities.
By doing so we enable MFA support, but no `AuthorizationManagerFactory` Bean is published.

include-code::./SelectiveMfaConfiguration[tag=enable-mfa,indent=0]

Next create an `AuthorizationManagerFactory` instance, but do not publish it as a Bean.

include-code::./SelectiveMfaConfiguration[tag=httpSecurity,indent=0]
<1> Create a `DefaultAuthorizationManagerFactory` as we did previously, but do not publish it as a Bean.
By not publishing it as a Bean, we are able to selectively use the `AuthorizationManagerFactory` instead of using it for every authorization rule.
<2> Explicitly use `AuthorizationManagerFactory` so that URLs that begin with `/admin/**` require `FACTOR_OTT`, `FACTOR_PASSWORD`, and `ROLE_ADMIN`.
<3> Explicitly use `AuthorizationManagerFactory` so that URLs that begin with `/user/settings` require `FACTOR_OTT` and `FACTOR_PASSWORD`
<4> Otherwise, the request must be authenticated.
There is no MFA requirement, because the `AuthorizationManagerFactory` is not used.
<5> Set up the authentication mechanisms that can provide the required factors.

[[valid-duration]]
== Specifying a Valid Duration

At times, we may want to define authorization rules based upon how recently we authenticated.
For example, an application may want to require that the user has authenticated within the last hour in order to allow access to the `/user/settings` endpoint.

Remember at the time of authentication, a `FactorGrantedAuthority` is added to the `Authentication`.
The `FactorGrantedAuthority` specifies when it was `issuedAt`, but does not describe how long it is valid for.
This is intentional, because it allows a single `FactorGrantedAuthority` to be used with different ``validDuration``s.

Let's take a look at an example that illustrates how to meet the following requirements:

- URLs that begin with `/admin/` should require that a password has been provided within the last 30 minutes
- URLs that being with `/user/settings` should require that a password has been provided within the last hour
- Otherwise, authentication is required, but it does not care if it is a password or how long ago authentication occurred

include-code::./ValidDurationConfiguration[tag=httpSecurity,indent=0]
<1> First we define `passwordIn30m` as a requirement for a password within 30 minutes
<2> Next, we define `passwordInHour` as a requirement for a password within an hour
<3> We use `passwordIn30m` to require that URLs that begin with `/admin/` should require that a password has been provided in the last 30 minutes and that the user has the `ROLE_ADMIN` authority
<4> We use `passwordInHour` to require that URLs that begin with `/user/settings` should require that a password has been provided in the last hour
<5> Otherwise, authentication is required, but it does not care if it is a password or how long ago authentication occurred
<6> Set up the authentication mechanisms that can provide the required factors.

[[all-factors-anyof]]
== AllRequiredFactorsAuthorizationManager.anyOf

In the previous examples, access requires satisfying that the user has authenticated with all factors.
There are times when an application wants to allow users to satisfy one of several different combinations of factors.
javadoc:org.springframework.security.authorization.AllRequiredFactorsAuthorizationManager#anyOf(AllRequiredFactorsAuthorizationManager...)[AllRequiredFactorsAuthorizationManager.anyOf] grants access if at least one of the provided combinations of factors is satisfied.

Consider a scenario where a user can authenticate with WebAuthn alone, or with both a password and a one-time token.

include-code::./AnyOfRequiredFactorsConfiguration[tag=httpSecurity,indent=0]
<1> Require WebAuthn
<2> Require both a password and a one-time token
<3> Combine the combinations of factors with `anyOf`, granting access if either is satisfied
<4> URLs that begin with `/protected/**` require the user to satisfy either combination of factors
<5> All other requests require only authentication
<6> Set up the authentication mechanisms that can provide the required factors

[[programmatic-mfa]]
== Programmatic MFA

In our previous examples, MFA is a static decision per request.
There are times when we might want to require MFA for some users, but not others.
Determining if MFA is enabled per user can be achieved by using `AuthorizationManagerFactories.multiFactor().when` to conditionally require factors based upon the `Authentication`.
This is implemented using xref:servlet/authorization/architecture.adoc#authz-conditional-authorization-manager[`ConditionalAuthorizationManager`].

To enable the conditional MFA rules globally, we can publish an `AuthorizationManagerFactory` Bean.

include-code::./AdminMfaAuthorizationManagerConfiguration[tag=authorizationManagerFactory,indent=0]
<1> Require `FACTOR_OTT` and `FACTOR_PASSWORD`
<2> Only apply the requirement if the username is `admin`. Otherwise, MFA is not required.
<3> Return the `AuthorizationManagerFactory` using `.build()`. Since it is published as a Bean, it is used globally.

This should feel very similar to our previous example in xref:./mfa.adoc#authorization-manager-factory[].
The difference is that in the previous example, the `AuthorizationManagerFactories` creates an `AuthorizationManager` that always requires the same authorities.

We can now define our authorization rules which are combined with `AuthorizationManagerFactory`.

include-code::./AdminMfaAuthorizationManagerConfiguration[tag=httpSecurity,indent=0]
<1> URLs that begin with `/admin/**` require `ROLE_ADMIN`.
If the username is `admin`, then `FACTOR_OTT` and `FACTOR_PASSWORD` are also required.
<2> Otherwise, the request must be authenticated.
If the username is `admin`, then `FACTOR_OTT` and `FACTOR_PASSWORD` are also required.

NOTE: MFA is enabled by username and not role because that is how we implemented `RequiredAuthoritiesAuthorizationManagerConfiguration`.
If we preferred, we could change our logic to enable MFA based upon the roles rather than the username.

[[raam-mfa]]
== RequiredAuthoritiesAuthorizationManager

We've demonstrated how we can dynamically determine the authorities for a particular user in xref:./mfa.adoc#programmatic-mfa[] using `AuthorizationManagerFactories.multiFactor().when`.
However, this is such a common scenario that Spring Security provides built in support using javadoc:org.springframework.security.authorization.RequiredAuthoritiesAuthorizationManager[] and javadoc:org.springframework.security.authorization.RequiredAuthoritiesRepository[].

Let's implement the same requirement that we did in xref:./mfa.adoc#programmatic-mfa[] using the built-in support.

We start by creating the `RequiredAuthoritiesAuthorizationManager` Bean to use.

include-code::./RequiredAuthoritiesAuthorizationManagerConfiguration[tag=authorizationManager,indent=0]
<1> Create a javadoc:org.springframework.security.authorization.MapRequiredAuthoritiesRepository[] that maps users with the username `admin` to require MFA.
<2> Return a `RequiredAuthoritiesAuthorizationManager` that is injected with the `MapRequiredAuthoritiesRepository`.

Next we can define an `AuthorizationManagerFactory` that uses the `RequiredAuthoritiesAuthorizationManager`.

include-code::./RequiredAuthoritiesAuthorizationManagerConfiguration[tag=authorizationManagerFactory,indent=0]
<1> Inject the `RequiredAuthoritiesAuthorizationManager` as the javadoc:org.springframework.security.authorization.DefaultAuthorizationManagerFactory#setAdditionalAuthorization(org.springframework.security.authorization.AuthorizationManager)[DefaultAuthorization.additionalAuthorization].
This instructs `DefaultAuthorizationManagerFactory` that any authorization rule should apply `RequiredAuthoritiesAuthorizationManager` along with any authorization requirements defined by the application (e.g. `hasRole("ADMIN")`).
<2> Publish `DefaultAuthorizationManagerFactory` as a Bean, so it is used globally

We can now define our authorization rules which are combined with `RequiredAuthoritiesAuthorizationManager`.

include-code::./RequiredAuthoritiesAuthorizationManagerConfiguration[tag=httpSecurity,indent=0]
<1> URLs that begin with `/admin/**` require `ROLE_ADMIN`.
If the username is `admin`, then `FACTOR_OTT` and `FACTOR_PASSWORD` are also required.
<2> Otherwise, the request must be authenticated.
If the username is `admin`, then `FACTOR_OTT` and `FACTOR_PASSWORD` are also required.

Our example uses an in memory mapping of usernames to the additional required authorities.
For more dynamic use cases that can be determined by the username, a custom implementation of javadoc:org.springframework.security.authorization.RequiredAuthoritiesRepository[] can be created.
Possible examples would be looking up if a user has enabled MFA in an explicit setting, determining if a user has registered a passkey, etc.

For cases that need to determine MFA based upon the `Authentication`, `AuthorizationManagerFactories.multiFactor().when` can be used as demonstrated in xref:./mfa.adoc#programmatic-mfa[].


[[hasallauthorities]]
== Using hasAllAuthorities

We've shown a lot of additional infrastructure for supporting MFA.
However, for simple MFA use-cases, using `hasAllAuthorities` to require multiple factors is effective.

include-code::./ListAuthoritiesConfiguration[tag=httpSecurity,indent=0]
<1> Require `FACTOR_PASSWORD` and `FACTOR_OTT` for every request
<2> Set up the authentication mechanisms that can provide the required factors.

The configuration above works well only for the most simple use-cases.
If you have lots of endpoints, you probably do not want to repeat the requirements for MFA in every authorization rule.

For example, consider the following configuration:

include-code::./MultipleAuthorizationRulesConfiguration[tag=httpSecurity,indent=0]
<1> For URLs that begin with `/admin/**`, the following authorities are required `FACTOR_OTT`, `FACTOR_PASSWORD`, `ROLE_ADMIN`.
<2> For every other URL, the following authorities are required `FACTOR_OTT`, `FACTOR_PASSWORD`, `ROLE_USER`.
<3> Set up the authentication mechanisms that can provide the required factors.

The configuration only specifies two authorization rules, but it is enough to see that the duplication is not desirable.
Can you imagine what it would be like to declare hundreds of rules like this?

What's more that it becomes difficult to express more complicated authorization rules.
For example, how would you require two factors and either `ROLE_ADMIN` or `ROLE_USER`?

The answer to these questions, as we have already seen, is to use xref:./mfa.adoc#emfa[]

[[re-authentication]]
== Re-authentication

The most common of these is re-authentication.
Imagine an application configured in the following way:

include-code::./SimpleConfiguration[tag=httpSecurity,indent=0]

By default, this application has two authentication mechanisms that it allows, meaning that the user could use either one and be fully-authenticated.

If there is a set of endpoints that require a specific factor, we can specify that in `authorizeHttpRequests` as follows:

include-code::./RequireOttConfiguration[tag=httpSecurity,indent=0]
<1> States that all `/profile/**` endpoints require one-time-token login to be authorized

Given the above configuration, users can log in with any mechanism that you support.
And, if they want to visit the profile page, then Spring Security will redirect them to the One-Time-Token Login page to obtain it.

In this way, the authority given to a user is directly proportional to the amount of proof given.
This adaptive approach allows users to give only the proof needed to perform their intended operations.


[[obtaining-more-authorization]]
== Authorizing More Scopes

You can also configure exception handling to direct Spring Security on how to obtain a missing scope.

Consider an application that requires a specific OAuth 2.0 scope for a given endpoint:

include-code::./ScopeConfiguration[tag=httpSecurity,indent=0]

If this is also configured with an `AuthorizationManagerFactory` bean like this one:

include-code::./MissingAuthorityConfiguration[tag=authorizationManagerFactoryBean,indent=0]

Then the application will require an X.509 certificate as well as authorization from an OAuth 2.0 authorization server.

In the event that the user does not consent to `profile:read`, this application as it stands will issue a 403.
However, if you have a way for the application to re-ask for consent, then you can implement this in an `AuthenticationEntryPoint` like the following:

include-code::./MissingAuthorityConfiguration[tag=authenticationEntryPoint,indent=0]

Then, your filter chain declaration can bind this entry point to the given authority like so:

include-code::./MissingAuthorityConfiguration[tag=httpSecurity,indent=0]
