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)
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`.
javadoc:org.springframework.security.config.annotation.authorization.EnableMultiFactorAuthentication[format=annotation] makes it easy to enable multifactor authentication.
=== 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].
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.
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:
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].
We have demonstrated how to configure an entire application to require MFA by using xref:./mfa.adoc#emfa[``@EnableMultiFactorAuthentication``s] `authorities` property.
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
<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.
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.
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`].
The difference is that in the previous example, the `AuthorizationManagerFactories` creates an `AuthorizationManager` that always requires the same authorities.
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[].
<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`.
<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")`).
<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[].