Default redirection URL is not evaluated for unauthenticated portal-root logins
Version
v4.39.20
Deployment Method
Kubernetes
Reverse Proxy
Traefik
Reverse Proxy Version
No response
Description
In Authelia v4.39.20, an unauthenticated user who navigates directly to the portal root without an rd parameter does not have the configured default_redirection_url evaluated as an authorization target after completing first-factor authentication.
The configured default URL is retrieved from configuration, but it is not evaluated against the authenticated user’s access-control policy in this flow.
The portal-root frontend path is relevant. In web/src/views/LoginPortal/LoginPortal.tsx:45-63, the redirect target is derived only from the rd query parameter:
const redirectionURL = useQueryParam(RedirectionURL);When rd is absent, LoginPortal.tsx:90-93 immediately exits the redirect path:
if (!redirectionURL) {
return false;
}The backend state endpoint does expose the configured default URL. internal/handlers/handler_state.go:22-30:
stateResponse := StateResponse{
Username: userSession.Username,
AuthenticationLevel: userSession.AuthenticationLevel(ctx.Configuration.WebAuthn.EnablePasskey2FA),
FactorKnowledge: userSession.AuthenticationMethodRefs.FactorKnowledge(),
}
if uri := ctx.GetDefaultRedirectionURL(); uri != nil {
stateResponse.DefaultRedirectionURL = uri.String()
}However, default_redirection_url is only declared in the frontend state type at web/src/services/State.ts:10-15:
export interface AutheliaState {
username: string;
authentication_level: AuthenticationLevel;
factor_knowledge: boolean;
default_redirection_url?: string;
}It is not otherwise consumed by the v4.39.20 web frontend. Consequently, loading the portal root does not use the value returned by /api/state as a redirect target.
After first-factor authentication, internal/handlers/handler_firstfactor_password.go:154-160 passes the request target and request method to Handle1FAResponse:
if len(bodyJSON.Flow) > 0 {
handleFlowResponse(ctx, &userSession, bodyJSON.FlowID, bodyJSON.Flow, bodyJSON.SubFlow, bodyJSON.UserCode)
} else {
Handle1FAResponse(ctx, bodyJSON.TargetURL, bodyJSON.RequestMethod, userSession.Username, userSession.Groups)
}For a direct portal-root login, bodyJSON.TargetURL is empty because no rd was supplied. The no-target branch in internal/handlers/response.go:25-40 currently handles this as follows:
// Handle1FAResponse handle the redirection upon 1FA authentication.
func Handle1FAResponse(ctx *middlewares.AutheliaCtx, targetURI, requestMethod, username string, groups []string) {
var err error
if len(targetURI) == 0 {
defaultRedirectionURL := ctx.GetDefaultRedirectionURL()
if !ctx.Providers.Authorizer.IsSecondFactorEnabled() && defaultRedirectionURL != nil {
if err = ctx.SetJSONBody(redirectResponse{Redirect: defaultRedirectionURL.String()}); err != nil {
ctx.Logger.Errorf("Unable to set default redirection URL in body: %s", err)
}
} else {
ctx.ReplyOK()
}
return
}This branch obtains the configured default URL, but does not pass it through the normal target authorization path in response.go:43-65:
if targetURL, err = url.ParseRequestURI(targetURI); err != nil {
ctx.Error(fmt.Errorf("unable to parse target URL %s: %w", targetURI, err), messageAuthenticationFailed)
return
}
_, requiredLevel := ctx.Providers.Authorizer.GetRequiredLevel(
authorization.Subject{
Username: username,
Groups: groups,
IP: ctx.RemoteIP(),
},
authorization.NewObject(targetURL, requestMethod))
if requiredLevel == authorization.TwoFactor {
ctx.Logger.Warnf("%s requires 2FA, cannot be redirected yet", targetURI)
ctx.ReplyOK()
return
}Instead, the no-target branch makes its decision using IsSecondFactorEnabled(). That reflects whether 2FA is enabled anywhere in the overall authorization configuration, rather than the authorization level required for the configured default_redirection_url.
When IsSecondFactorEnabled() is true, Handle1FAResponse returns 200 OK without a redirect. In LoginPortal.tsx:176-182, first-factor success then refreshes state rather than navigating:
const handleAuthSuccess = async (redirectionURL: string | undefined) => {
if (redirectionURL) {
redirector(redirectionURL);
} else {
fetchState();
}
};Finally, the portal routes a 1FA session to an available second-factor method in LoginPortal.tsx:119-140, because handleRedirection() has already returned false and second-factor methods are available.
The result is that, for a direct unauthenticated portal-root login with no rd, the configured default_redirection_url is retrieved but never evaluated as the effective authorization target before authentication completion.
Reproduction
In the configuration, set a default_redirect_url with 1 factor or bypass policy and apply configuration.
- Log out of Authelia / open new incognito window
- Naviagte directly to Authelia base url
- Log in with username and password
- You should now be on the 2nd factor page (either the set up second factor page or the authenticate with the second factor page)
Expectations
For an unauthenticated portal-root login with no rd parameter, Authelia should treat the configured default_redirection_url as the effective post-authentication target.
Before deciding whether authentication is complete after 1FA, Authelia should evaluate that target using the same authorization path used for an explicit rd target: the authenticated subject, groups, source IP, destination URL, and the GET method used for the browser navigation.
The result should depend on the policy required by the configured default URL:
- If the default URL requires bypass or one_factor, Authelia should return it as the post-1FA redirect.
- If the default URL requires two_factor, Authelia should continue to the second-factor flow. Following successful 2FA, it should redirect to the configured default URL.
- If no default_redirection_url is configured, Authelia should retain the current no-target behavior and remain at the portal.
- The configured default URL should continue to be subject to existing safe-redirection validation.
- The presence of unrelated two_factor policies or OIDC clients should not determine whether a first-factor-authenticated user can be redirected to a default URL which itself only requires bypass or one_factor.
Configuration (Authelia)
theme: dark
default_2fa_method: webauthn
server:
address: 'tcp://:9091'
log:
level: 'debug'
totp:
disable: true
webauthn:
enable_passkey_login: true
selection_criteria:
discoverability: 'preferred'
user_verification: 'preferred'
duo_api:
hostname: '{{ secret "/authelia-secrets/duo.hostname" }}'
integration_key: '{{ secret "/authelia-secrets/duo.integration-key" }}'
secret_key: '{{ secret "/authelia-secrets/duo.secret-key" }}'
enable_self_enrollment: true
identity_validation:
elevated_session:
skip_second_factor: true
reset_password:
jwt_secret: '{{ secret "/authelia-secrets/authelia.password-reset-jwt-secret" }}'
authentication_backend:
password_reset:
disable: false
ldap:
implementation: 'lldap'
address: 'ldaps://lldap.lldap.svc.cluster.local'
base_dn: 'dc=example,dc=com'
user: 'uid=authelia,ou=people,dc=example,dc=com'
users_filter: '(&({username_attribute}={input})(objectClass=person)(memberOf=cn=users,ou=groups,dc=example,dc=com))'
password: '{{ secret "/authelia-secrets/ldap.password" }}'
tls:
server_name: 'ldap.example.com'
skip_verify: false
session:
secret: '{{ secret "/authelia-secrets/authelia.session-secret" }}'
cookies:
- domain: 'example.com'
authelia_url: 'https://sso.example.com'
default_redirection_url: 'https://wiki.example.com/user-guides/'
redis:
host: 'redis.homelab'
port: 6379
password: '{{ secret "/authelia-secrets/database.redis-password" }}'
database_index: 3
storage:
encryption_key: '{{ secret "/authelia-secrets/authelia.storage-encryption-key" }}'
postgres:
address: 'tcp://postgres.homelab:5432'
database: 'authelia'
username: 'authelia'
password: '{{ secret "/authelia-secrets/database.postgres.authelia-password" }}'
access_control:
default_policy: deny
rules:
- domain:
- "wiki.example.com"
resources:
- "^/user-guides/?$"
policy: bypass
- domain:
- "wiki.example.com"
subject: "group:users"
policy: one_factorBuild Information
Last Tag: v4.39.20
State: tagged clean
Branch: v4.39.20
Commit: 1b524f7f4bbf7b5637f4c6b98f4f66fd4b4aed91
Build Number: 55433
Build OS: linux
Build Arch: arm64
Build Compiler: gc
Build Date: Tue, 26 May 2026 20:16:15 +1000
Development: false
Extra:
Go:
Version: go1.26.3
Module Path: github.com/authelia/authelia/v4
Executable Path: github.com/authelia/authelia/v4/cmd/autheliaLogs (Authelia)
❱ kubectl logs -n authelia authelia-55f9bf97f8-z8hnk
time="2026-08-24T01:54:22Z" level=debug msg="Loaded Configuration Sources" files="[/config]" filters="[template]"
time="2026-08-24T01:54:22Z" level=debug msg="Logging Initialized" fields.level=debug file= format= keep_stdout=false
time="2026-08-24T01:54:22Z" level=debug msg="Process user information" gid=1000 uid=1000
time="2026-08-24T01:54:22Z" level=info msg="Authelia v4.39.20 is starting"
time="2026-08-24T01:54:22Z" level=info msg="Log severity set to debug"
time="2026-08-24T01:54:22Z" level=debug msg="Registering OpenID Connect 1.0 client with client id 'audiobookshelf' and policy 'one_factor'"
time="2026-08-24T01:54:22Z" level=debug msg="Registering OpenID Connect 1.0 client with client id 'matrix-authentication-service' and policy 'secure'"
time="2026-08-24T01:54:22Z" level=debug msg="Registering OpenID Connect 1.0 client with client id 'netbox' and policy 'one_factor'"
time="2026-08-24T01:54:22Z" level=debug msg="Registering OpenID Connect 1.0 client with client id 'portainer' and policy 'secure'"
time="2026-08-24T01:54:22Z" level=debug msg="Registering OpenID Connect 1.0 client with client id 'hermes-dashboard' and policy 'secure'"
time="2026-08-24T01:54:22Z" level=debug msg="Registering OpenID Connect 1.0 client with client id 'gitlab' and policy 'two_factor'"
time="2026-08-24T01:54:22Z" level=debug msg="Registering OpenID Connect 1.0 client with client id 'proxmox' and policy 'secure'"
time="2026-08-24T01:54:22Z" level=info msg="Storage schema is being checked for updates"
time="2026-08-24T01:54:22Z" level=info msg="Storage schema is already up to date"
time="2026-08-24T01:54:24Z" level=debug msg="LDAP Discovery. LDAP Version: 3; Controls: 1.3.6.1.4.1.4203.1.11.1, 1.3.6.1.4.1.4203.1.11.3; Extensions: none; Features: 1.3.6.1.4.1.4203.1.5.1; SASL Mechanisms: none; Vendor Name: LLDAP; Vendor Version: lldap_0.1.0"
time="2026-08-24T01:54:24Z" level=debug msg="webauthn-metadata provider: startup check skipped as it is disabled"
time="2026-08-24T01:54:25Z" level=info msg="Startup complete"
time="2026-08-24T01:54:25Z" level=info msg="Listening for non-TLS connections on '[::]:9091' path '/'" server=main service=server
time="2026-08-24T01:54:59Z" level=debug msg="Mark 1FA authentication attempt made by user 'test'" method=POST path=/api/firstfactor remote_ip=10.12.55.253
time="2026-08-24T01:54:59Z" level=debug msg="Successful 1FA authentication attempt made by user 'test'" method=POST path=/api/firstfactor remote_ip=10.12.55.253Logs (Proxy / Application)
Documentation
No response
Generative AI
Yes
Pre-Submission Checklist
I agree to follow the Code of Conduct
This is a bug report and not a support request
I have read the security policy and this bug report is not a security issue or security related issue
I have either included the complete configuration file or I am sure it's unrelated to the configuration
I have either included the complete debug / trace logs or the output of the build-info command if the logs are not relevant
I have provided all of the required information in full with the only alteration being reasonable sanitization in accordance with the Troubleshooting Sanitization reference guide
I have checked for related proxy or application logs and included them if available
I have checked for related issues and checked the documentation
Source: authelia/authelia