Troubleshooting
How to debug and solve common issues related to the bundle.
TOTP / Google Authenticator code is not accepted
The principle of TOTP/Google Authenticator is that both systems - the server and your device - generate the same authentication code from a shared secret and the current time. If one of those two components isn't in sync, they'll generate a different code. Therefore:
- Most common problem: Make sure the server time and the time on your device are in sync with the actual current time
- Make sure the secret used in your device matches the secret configured for the account on the server
- If you're using TOTP, make sure the app is actually supporting the specific TOTP configuration you're using. The Google Authenticator app supports only one specific TOTP configuration (6-digit code, 30sec window, sha1 algorithm)
The generated authentication code has a time window in which it is valid (30 seconds in Google Authenticator, for TOTP it depends on your configuration). The bigger the time difference between server and device, the smaller the time window, the higher the chance that the codes generated on server and from the app don't match up. When the time difference becomes larger than the time window, it becomes impossible to provide the right code.
To counteract the issue of time differences you could increase the leeway
or window
(deprecated) setting,
then more codes around the current time window will be accepted:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# config/packages/scheb_2fa.yaml
scheb_two_factor:
# For TOTP
totp:
window: 1 # [DEPRECATED since v6.11, will be removed in v7] Use "leeway", if possible
# Behavior depends on the version of Spomky-Labs/otphp used:
# - Until v10: How many codes before/after the current one would be accepted
# - From v11: Acceptable time drift in seconds
leeway: 0 # Acceptable time drift in seconds, requires Spomky-Labs/otphp v11 to be used
# Must be less or equal than the TOTP code's period
# If configured, takes precedence over the "window" option
# For Google Authenticator
google:
window: 1 # [DEPRECATED since v6.11, will be removed in v7] Use "leeway", if possible
# Behavior depends on the version of Spomky-Labs/otphp used:
# - Until v10: How many codes before/after the current one would be accepted
# - From v11: Acceptable time drift in seconds
leeway: 0 # Acceptable time drift in seconds, requires Spomky-Labs/otphp v11 to be used
# Must be less or equal than 30 seconds
# If configured, takes precedence over the "window" option
You might want to configure a time synchronization service, such as ntpdate
on your server to make sure your server
time is always in sync with UTC.
The Google Authenticator app has an option to sync the time of your device. Open the app and select
Settings > Time correction for codes > Sync now
from the menu. Other apps might have a similar option.
Logout redirects back to the two-factor authentication form
Problem
When the two-factor authentication form is shown, you want to cancel the two-factor authentication process. You click the "cancel" link, which should execute a logout. It does not execute the logout, but redirects back to the two-factor authentication form.
Solution
If you see such behavior, the access_control
rules from the security configuration don't allow accessing the logout
path. Your logout path must be accessible to user with any authentication state, which is usually done by allowing it
for PUBLIC_ACCESS
. You're most likely missing a rule under access_control
in the security
configuration.
The configuration should look similar to this:
1 2 3 4 5
# config/packages/security.yaml
security:
access_control:
- { path: ^/logout, role: PUBLIC_ACCESS }
# More rules here ...
Make sure the rule comes first in the list, since access control rules are evaluated in order.
If you have such a rule and it still doesn't work, for some reason the rule is not matching. Make absolutely sure the
path
regular expression matches your logout path. If you have additional options, such as host
or ip
, check
that they're matching as well.
Not logged in after completing two-factor authentication
Problem
After you logged in and have successfully passed the two-factor authentication process, you're not logged in. Either you are redirected back to the login page or the page is shown with the authenticated user missing.
Troubleshooting
Disable two-factor authentication by commenting out all
two_factor
settings in the security firewall configuration. Try to login. Does it work?- Yes, it works
- Continue with 2)
- No, it does not work
-
Your login process is broken.
Solution: Can't exactly tell what's wrong. Continue debugging the login issue. Solve this issue first, before you re-enable two-factor authentication.
Revert the changes from 1). Debug the security token on the two-factor authentication form page by
var_dump
-ing it or any other suitable method.The token should be of type
TwoFactorToken
and the fieldauthenticatedToken
should contain an authenticated security token. Does that authenticated token haveauthenticated
=false
set?- Yes
- Your authenticated token was flagged as invalid. Follow the solution below.
- No
- Continue with 3)
After completing two-factor authentication, when you end up in the unauthenticated state, check the last request few requests in the Symfony profiler.
For each of the requests, go to Logs -> Debug.
Does it say
Cannot refresh token because user has changed
orToken was deauthenticated after trying to refresh it
?- Yes
- Your authenticated token was flagged as invalid. Follow the solution below.
- No
- Unknown issue. Try to reach out for help by creating an issue and let us know what you've already tested.
Solution to: Your authenticated token was flagged as invalid
Most likely your user entity implements the a serializable interface and not all of the fields relevant to the
authentication process are taken by serialize/unserialize. Check which fields are used in methods serialize()
and
deserialize()
.
It must be at least the fields that are used in the methods from Symfony
.
If your user entity implements Symfony
, you also need the fields
that are used in isAccountNonExpired()
, isAccountNonLocked()
, isCredentialsNonExpired()
and isEnabled()
.
Two-factor authentication form is not shown after login
Problem
After successful login, the two-factor authentication form is not shown. Instead, you're either logged in or you see a different page from your application.
Basic checks
- Your login page belongs to the firewall, which has two-factor authentication configured.
- The paths of login page, login check, 2fa and 2fa check are all located with the firewall's path
pattern
. - Your user entity has the interfaces implemented, which are necessary for the two-factor authentication method.
Your user entity fulfills the requirements of at least one two-factor authentication method:
- The
is*Enabled()
method returnstrue
- Additional data for the authentication method is returned, e.g. for Google Authenticator to work the
getGoogleAuthenticatorSecret()
method must return a secret code.
- The
Is access_control
configured properly?
To make the two-factor authentication form accessible during the two-factor authentication process, you have to
configure a access_control
rule for the 2fa routes:
The configuration should look similar to this:
1 2 3 4 5 6 7 8
# config/packages/security.yaml
security:
# IMPORTANT: THE ACCESS CONTROL RULE NEEDS TO BE AT THE VERY TOP OF THE LIST!
access_control:
# This ensures that the form can only be accessed when two-factor authentication is in progress.
# The path may be different, depending on how you've configured the route.
- { path: ^/2fa, role: IS_AUTHENTICATED_2FA_IN_PROGRESS }
# Other rules may follow here...
Make sure the rule comes first in the list, since access control rules are evaluated in order.
If you already have such a rule at the top of the list, make sure the path
regular expression matches your
two-factor authentication form path. If you have additional options, such as host
or ip
, check that they're
matching as well.
Is there something special about your security setup?
Often issues originate from a customization in the application's security setup, which is usually related to how roles are granted. Examples of such issue are:
- Roles are dynamically granted by a voter, which isn't aware of the intermediate 2fa state
- Roles are loaded by replacing the security token after login, effectively skipping 2fa
- An exception thrown in a voter
For 2fa to work properly, there must be two things fulfilled: A TwoFactorToken
must be present after login and
within that intermediate "2fa incomplete" state no roles must be granted. That later one is achieved by
TwoFactorToken
not returning any roles on the getRoleNames()
call. But if you grant roles differently other than
through the token, things will break.
The solution to this problem is usually to skip any customization for a security token of type
TwoFactorTokenInterface
.
1 2 3 4 5
use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface;
if (!($token instanceof TwoFactorTokenInterface)) {
// Your customization here
}
Troubleshooting
Is a
TwoFactorToken
present after the login?- Yes
- Continue with 2)
- No
- Continue with 3)
Try accessing a page that requires the user to be authenticated. Does it redirect to the two-factor authentication form?
- Yes
-
Solution: The page you've seen after login doesn't require a fully authenticated user. Most likely that
path is accessible to
PUBLIC_ACCESS
via your securityaccess_control
configuration. Either change youraccess_control
configuration or after login force-redirect to user to a page that requires full authentication. - No
- Unknown issue. Try to reach out for help by creating an issue and let us know what you've already tested.
On login, do you reach the end (return statement) of method
Scheb
?\TwoFactorBundle \Security \Authentication \Provider \AuthenticationProviderDecorator::authenticate() - Yes
- Continue with 4)
- No
- Something is wrong with the integration of the bundle. Try to reach out for help by creating an issue`_ and let us know what you've already tested.
On login, is method
Scheb
called?\TwoFactorBundle \Security \TwoFactor \Handler \TwoFactorProviderInitiator::getActiveTwoFactorProviders() - Yes, it's called
- Continue with 5)
- No it's not called
- Solution: Two-factor authentication is skipped, either because of the IP whitelist or because of a trusted device token. IP whitelist is part of the bundle's configuration. Maybe you have whitelisted "localhost" or "127.0.0.1"? The trusted device cookie can be removed with your browser's developer tools.
Does
Scheb
return any values?\TwoFactorBundle \Security \TwoFactor \Handler \TwoFactorProviderInitiator::getActiveTwoFactorProviders() - Yes, it returns an array of strings
- Unknown issue. Try to reach out for help by creating an issue and let us know what you've already tested.
- No, it returns an empty array
-
Solution: our user doesn't have an active two-factor authentication method. Either the
is*Enabled
method returnsfalse
or an essential piece of data (e.g. Google Authenticator secret) is missing.
Trusted device cookie is not set
Problem
After you have completed 2fa, you expect that device to be flagged as a "trusted device", but the trusted device cookie is not set.
Basic checks
- 2fa was completed with that call and you've been fully authenticated afterwards.
- Together with the 2fa code, you have sent the trusted parameter (default
_trusted
) with atrue
-like value. (Background information: Devices are not automatically flagged as trusted. The user has to choose if they can trust that device. That's why this extra parameter has to be sent over.)
Troubleshooting
Have a look at the response of the HTTP call when you sent the 2fa and the trusted parameter. Do you see a cookie
being set (Set-Cookie
header)?
- Yes
- Please validate the cookie's parameters. Make sure everything is fine for that cookie: the path, domain, and other cookie options. Did you maybe try to set it for a top level domain ?
- No, there's no cookie set
- Unknown issue. Try to reach out for help by creating an issue and let us know what you've already tested.