[FEATURE] Add Remote-Name and Remote-Email headers (#1402)

pull/1413/head
Amir Zarrinkafsh 2020-10-26 22:38:08 +11:00 committed by GitHub
parent 9891f99752
commit a83ccd7188
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 96 additions and 52 deletions

View File

@ -20,7 +20,7 @@ services:
- 'traefik.http.routers.authelia.tls.certresolver=letsencrypt'
- 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://auth.example.com'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email'
expose:
- 9091
restart: unless-stopped

View File

@ -20,7 +20,7 @@ services:
- 'traefik.http.routers.authelia.tls.options=default'
- 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://authelia.example.com'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email'
expose:
- 9091
restart: unless-stopped

View File

@ -114,11 +114,15 @@ backend be_authelia
server authelia authelia:9091
backend be_nextcloud
# Pass Remote-User and Remote-Groups headers
# Pass Remote-User, Remote-Name, Remote-Email and Remote-Groups headers
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found
acl remote_name_exist var(req.auth_response_header.remote_name) -m found
acl remote_email_exist var(req.auth_response_header.remote_email) -m found
http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist
http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
server nextcloud nextcloud:443 ssl verify none
```
@ -179,11 +183,15 @@ listen authelia_proxy
server authelia authelia:9091 ssl verify none
backend be_nextcloud
# Pass Remote-User and Remote-Groups headers
# Pass Remote-User, Remote-Name, Remote-Email and Remote-Groups headers
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found
acl remote_name_exist var(req.auth_response_header.remote_name) -m found
acl remote_email_exist var(req.auth_response_header.remote_email) -m found
http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist
http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
server nextcloud nextcloud:443 ssl verify none
```

View File

@ -35,8 +35,8 @@ appear in the configuration examples.
## How can the backend be aware of the authenticated users?
The only way Authelia can share information about the authenticated user currently is through the use of two HTTP headers:
`Remote-User` and `Remote-Groups`.
The only way Authelia can share information about the authenticated user currently is through the use of four HTTP headers:
`Remote-User`, `Remote-Name`, `Remote-Email` and `Remote-Groups`.
Those headers are returned by Authelia on requests to `/api/verify` and must be forwarded by the reverse proxy to the backends
needing them. The headers will be provided with each call to the backend once the user is authenticated.
Please note that the backend must support the use of those headers to leverage that information, many

View File

@ -81,8 +81,12 @@ auth_request_set $target_url $scheme://$http_host$request_uri;
# proxy. In the future, it's gonna be safe to just use OAuth.
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $name $upstream_http_remote_name;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Name $name;
proxy_set_header Remote-Email $email;
# If Authelia returns 401, then nginx redirects the user to the login portal.
# If it returns 200, then the request pass through to the backend.
# For other type of errors, nginx will handle them as usual.

View File

@ -86,7 +86,7 @@ services:
- 'traefik.frontend.rule=Host:nextcloud.example.com'
- 'traefik.frontend.auth.forward.address=http://authelia:9091/api/verify?rd=https://login.example.com/'
- 'traefik.frontend.auth.forward.trustForwardHeader=true'
- 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups'
- 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups,Remote-Name,Remote-Email'
expose:
- 443
restart: unless-stopped

View File

@ -76,7 +76,7 @@ services:
- 'traefik.http.routers.authelia.tls=true'
- 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://login.example.com/'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email'
expose:
- 9091
restart: unless-stopped
@ -120,7 +120,7 @@ This can be avoided a couple different ways:
```yaml
- 'traefik.http.middlewares.authelia.forwardauth.address=http://authelia:9091/api/verify?rd=https://login.example.com/'
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups'
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email'
```
[Traefik 2.x]: https://docs.traefik.io/

View File

@ -47,5 +47,5 @@ If properly configured, Authelia guarantees the following for security of your u
It's important to note that Authelia is considered running in a trusted environment for two reasons
1. Requests coming to Authelia should be initiated by reverse proxies but CAN be initiated by any other server currently. There is no trusted relationship between Authelia and the reverse proxy so an attacker within the network could abuse Authelia and attack it.
2. Your environment should be considered trusted especially if you're using the `Remote-User` and `Remote-Groups` headers to forward authentication data to your backends. These headers are transmitted plain and unsigned to the backends, meaning a malicious user within the network could pretend to be Authelia and send those headers to bypass authentication and gain access to the service. A mitigation could be to transmit those headers with a digital signature which could be verified by the backend however, many backends just don't support it. It has therefore been decided to invest on OpenID Connect instead to solve that authentication delegation problem. Indeed, many backends
2. Your environment should be considered trusted especially if you're using the `Remote-User`, `Remote-Name`, `Remote-Email` and `Remote-Groups` headers to forward authentication data to your backends. These headers are transmitted plain and unsigned to the backends, meaning a malicious user within the network could pretend to be Authelia and send those headers to bypass authentication and gain access to the service. A mitigation could be to transmit those headers with a digital signature which could be verified by the backend however, many backends just don't support it. It has therefore been decided to invest on OpenID Connect instead to solve that authentication delegation problem. Indeed, many backends
do support OAuth2 though since it has become a standard lately.

View File

@ -14,6 +14,8 @@ const authPrefix = "Basic "
// AuthorizationHeader is the basic-auth HTTP header Authelia utilises.
const AuthorizationHeader = "Proxy-Authorization"
const remoteUserHeader = "Remote-User"
const remoteNameHeader = "Remote-Name"
const remoteEmailHeader = "Remote-Email"
const remoteGroupsHeader = "Remote-Groups"
var protoHostSeparator = []byte("://")

View File

@ -125,39 +125,41 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
// verifyBasicAuth verify that the provided username and password are correct and
// that the user is authorized to target the resource.
func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCtx) (username string, groups []string, authLevel authentication.Level, err error) { //nolint:unparam
func verifyBasicAuth(auth []byte, targetURL url.URL, ctx *middlewares.AutheliaCtx) (username, name string, groups, emails []string, authLevel authentication.Level, err error) { //nolint:unparam
username, password, err := parseBasicAuth(string(auth))
if err != nil {
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to parse content of %s header: %s", AuthorizationHeader, err)
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to parse content of %s header: %s", AuthorizationHeader, err)
}
authenticated, err := ctx.Providers.UserProvider.CheckUserPassword(username, password)
if err != nil {
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check credentials extracted from %s header: %s", AuthorizationHeader, err)
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check credentials extracted from %s header: %s", AuthorizationHeader, err)
}
// If the user is not correctly authenticated, send a 401.
if !authenticated {
// Request Basic Authentication otherwise
return "", nil, authentication.NotAuthenticated, fmt.Errorf("User %s is not authenticated", username)
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("User %s is not authenticated", username)
}
details, err := ctx.Providers.UserProvider.GetDetails(username)
if err != nil {
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to retrieve details of user %s: %s", username, err)
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to retrieve details of user %s: %s", username, err)
}
return username, details.Groups, authentication.OneFactor, nil
return username, details.DisplayName, details.Groups, details.Emails, authentication.OneFactor, nil
}
// setForwardedHeaders set the forwarded User and Groups headers.
func setForwardedHeaders(headers *fasthttp.ResponseHeader, username string, groups []string) {
// setForwardedHeaders set the forwarded User, Groups, Name and Email headers.
func setForwardedHeaders(headers *fasthttp.ResponseHeader, username, name string, groups, emails []string) {
if username != "" {
headers.Set(remoteUserHeader, username)
headers.Set(remoteGroupsHeader, strings.Join(groups, ","))
headers.Set(remoteNameHeader, name)
headers.Set(remoteEmailHeader, emails[0])
}
}
@ -183,28 +185,28 @@ func hasUserBeenInactiveTooLong(ctx *middlewares.AutheliaCtx) (bool, error) { //
// verifySessionCookie verifies if a user is identified by a cookie.
func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userSession *session.UserSession, refreshProfile bool,
refreshProfileInterval time.Duration) (username string, groups []string, authLevel authentication.Level, err error) {
refreshProfileInterval time.Duration) (username, name string, groups, emails []string, authLevel authentication.Level, err error) {
// No username in the session means the user is anonymous.
isUserAnonymous := userSession.Username == ""
if isUserAnonymous && userSession.AuthenticationLevel != authentication.NotAuthenticated {
return "", nil, authentication.NotAuthenticated, fmt.Errorf("An anonymous user cannot be authenticated. That might be the sign of a compromise")
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("An anonymous user cannot be authenticated. That might be the sign of a compromise")
}
if !userSession.KeepMeLoggedIn && !isUserAnonymous {
inactiveLongEnough, err := hasUserBeenInactiveTooLong(ctx)
if err != nil {
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check if user has been inactive for a long time: %s", err)
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to check if user has been inactive for a long time: %s", err)
}
if inactiveLongEnough {
// Destroy the session a new one will be regenerated on next request.
err := ctx.Providers.SessionProvider.DestroySession(ctx.RequestCtx)
if err != nil {
return "", nil, authentication.NotAuthenticated, fmt.Errorf("Unable to destroy user session after long inactivity: %s", err)
return "", "", nil, nil, authentication.NotAuthenticated, fmt.Errorf("Unable to destroy user session after long inactivity: %s", err)
}
return userSession.Username, userSession.Groups, authentication.NotAuthenticated, fmt.Errorf("User %s has been inactive for too long", userSession.Username)
return userSession.Username, userSession.DisplayName, userSession.Groups, userSession.Emails, authentication.NotAuthenticated, fmt.Errorf("User %s has been inactive for too long", userSession.Username)
}
}
@ -216,13 +218,13 @@ func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userS
ctx.Logger.Error(fmt.Errorf("Unable to destroy user session after provider refresh didn't find the user: %s", err))
}
return userSession.Username, userSession.Groups, authentication.NotAuthenticated, err
return userSession.Username, userSession.DisplayName, userSession.Groups, userSession.Emails, authentication.NotAuthenticated, err
}
ctx.Logger.Warnf("Error occurred while attempting to update user details from LDAP: %s", err)
}
return userSession.Username, userSession.Groups, userSession.AuthenticationLevel, nil
return userSession.Username, userSession.DisplayName, userSession.Groups, userSession.Emails, userSession.AuthenticationLevel, nil
}
func handleUnauthorized(ctx *middlewares.AutheliaCtx, targetURL fmt.Stringer, username string) {
@ -409,9 +411,9 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques
return
}
var username string
var username, name string
var groups []string
var groups, emails []string
var authLevel authentication.Level
@ -420,9 +422,9 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques
userSession := ctx.GetSession()
if isBasicAuth {
username, groups, authLevel, err = verifyBasicAuth(proxyAuthorization, *targetURL, ctx)
username, name, groups, emails, authLevel, err = verifyBasicAuth(proxyAuthorization, *targetURL, ctx)
} else {
username, groups, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession,
username, name, groups, emails, authLevel, err = verifySessionCookie(ctx, targetURL, &userSession,
refreshProfile, refreshProfileInterval)
}
@ -449,7 +451,7 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques
case NotAuthorized:
handleUnauthorized(ctx, targetURL, username)
case Authorized:
setForwardedHeaders(&ctx.Response.Header, username, groups)
setForwardedHeaders(&ctx.Response.Header, username, name, groups, emails)
}
if err := updateActivityTimestamp(ctx, isBasicAuth, username); err != nil {

View File

@ -204,7 +204,7 @@ func TestShouldVerifyWrongCredentials(t *testing.T) {
Return(false, nil)
url, _ := url.ParseRequestURI("https://test.example.com")
_, _, _, err := verifyBasicAuth([]byte("Basic am9objpwYXNzd29yZA=="), *url, mock.Ctx)
_, _, _, _, _, err := verifyBasicAuth([]byte("Basic am9objpwYXNzd29yZA=="), *url, mock.Ctx)
assert.Error(t, err)
}
@ -416,6 +416,7 @@ func TestShouldVerifyFailingDetailsFetchingInBasicAuth(t *testing.T) {
type Pair struct {
URL string
Username string
Emails []string
AuthenticationLevel authentication.Level
ExpectedStatusCode int
}
@ -427,23 +428,23 @@ func (p Pair) String() string {
func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
testCases := []Pair{
{"https://test.example.com", "", authentication.NotAuthenticated, 401},
{"https://bypass.example.com", "", authentication.NotAuthenticated, 200},
{"https://one-factor.example.com", "", authentication.NotAuthenticated, 401},
{"https://two-factor.example.com", "", authentication.NotAuthenticated, 401},
{"https://deny.example.com", "", authentication.NotAuthenticated, 401},
{"https://test.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://bypass.example.com", "", nil, authentication.NotAuthenticated, 200},
{"https://one-factor.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://two-factor.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://deny.example.com", "", nil, authentication.NotAuthenticated, 401},
{"https://test.example.com", "john", authentication.OneFactor, 403},
{"https://bypass.example.com", "john", authentication.OneFactor, 200},
{"https://one-factor.example.com", "john", authentication.OneFactor, 200},
{"https://two-factor.example.com", "john", authentication.OneFactor, 401},
{"https://deny.example.com", "john", authentication.OneFactor, 403},
{"https://test.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 403},
{"https://bypass.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 200},
{"https://one-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 200},
{"https://two-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 401},
{"https://deny.example.com", "john", []string{"john.doe@example.com"}, authentication.OneFactor, 403},
{"https://test.example.com", "john", authentication.TwoFactor, 403},
{"https://bypass.example.com", "john", authentication.TwoFactor, 200},
{"https://one-factor.example.com", "john", authentication.TwoFactor, 200},
{"https://two-factor.example.com", "john", authentication.TwoFactor, 200},
{"https://deny.example.com", "john", authentication.TwoFactor, 403},
{"https://test.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 403},
{"https://bypass.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200},
{"https://one-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200},
{"https://two-factor.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 200},
{"https://deny.example.com", "john", []string{"john.doe@example.com"}, authentication.TwoFactor, 403},
}
for _, testCase := range testCases {
@ -454,6 +455,7 @@ func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
userSession := mock.Ctx.GetSession()
userSession.Username = testCase.Username
userSession.Emails = testCase.Emails
userSession.AuthenticationLevel = testCase.AuthenticationLevel
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
@ -466,8 +468,10 @@ func TestShouldVerifyAuthorizationsUsingSessionCookie(t *testing.T) {
if testCase.ExpectedStatusCode == 200 && testCase.Username != "" {
assert.Equal(t, []byte(testCase.Username), mock.Ctx.Response.Header.Peek("Remote-User"))
assert.Equal(t, []byte("john.doe@example.com"), mock.Ctx.Response.Header.Peek("Remote-Email"))
} else {
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek("Remote-User"))
assert.Equal(t, []byte(nil), mock.Ctx.Response.Header.Peek("Remote-Email"))
}
})
}
@ -544,6 +548,7 @@ func TestShouldKeepSessionWhenUserCheckedRememberMeAndIsInactiveForTooLong(t *te
userSession := mock.Ctx.GetSession()
userSession.Username = testUsername
userSession.Emails = []string{"john.doe@example.com"}
userSession.AuthenticationLevel = authentication.TwoFactor
userSession.LastActivity = 0
userSession.KeepMeLoggedIn = true
@ -575,6 +580,7 @@ func TestShouldKeepSessionWhenInactivityTimeoutHasNotBeenExceeded(t *testing.T)
userSession := mock.Ctx.GetSession()
userSession.Username = testUsername
userSession.Emails = []string{"john.doe@example.com"}
userSession.AuthenticationLevel = authentication.TwoFactor
userSession.LastActivity = past.Unix()
mock.Ctx.SaveSession(userSession) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.

View File

@ -24,6 +24,7 @@ authentication_backend:
groups_filter: (&(member={dn})(objectclass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
user: cn=admin,dc=example,dc=com
password: password

View File

@ -64,8 +64,12 @@ backend fe_authelia
backend be_httpbin
acl remote_user_exist var(req.auth_response_header.remote_user) -m found
acl remote_groups_exist var(req.auth_response_header.remote_groups) -m found
acl remote_name_exist var(req.auth_response_header.remote_name) -m found
acl remote_email_exist var(req.auth_response_header.remote_email) -m found
http-request set-header Remote-User %[var(req.auth_response_header.remote_user)] if remote_user_exist
http-request set-header Remote-Groups %[var(req.auth_response_header.remote_groups)] if remote_groups_exist
http-request set-header Remote-Name %[var(req.auth_response_header.remote_name)] if remote_name_exist
http-request set-header Remote-Email %[var(req.auth_response_header.remote_email)] if remote_email_exist
server httpbin-backend httpbin:8000

View File

@ -11,7 +11,7 @@ services:
- 'traefik.frontend.auth.forward.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080/'
- 'traefik.frontend.auth.forward.tls.insecureSkipVerify=true'
- 'traefik.frontend.auth.forward.trustForwardHeader=true'
- 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups'
- 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email'
# Traefik 2.x
- 'traefik.http.routers.httpbin.rule=Host(`public.example.com`) && Path(`/headers`)'
- 'traefik.http.routers.httpbin.priority=150'

View File

@ -7,7 +7,7 @@ services:
- 'traefik.frontend.auth.forward.address=https://authelia-backend:9091/api/verify?rd=https://login.example.com:8080' # Traefik 1.x
- 'traefik.frontend.auth.forward.tls.insecureSkipVerify=true' # Traefik 1.x
- 'traefik.frontend.auth.forward.trustForwardHeader=true' # Traefik 1.x
- 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User,Remote-Groups' # Traefik 1.x
- 'traefik.frontend.auth.forward.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' # Traefik 1.x
- 'traefik.http.routers.protectedapps.rule=Host(`home.example.com`, `public.example.com`, `secure.example.com`, `admin.example.com`, `singlefactor.example.com`)' # Traefik 2.x
- 'traefik.http.routers.protectedapps.entrypoints=https' # Traefik 2.x
- 'traefik.http.routers.protectedapps.tls=true' # Traefik 2.x

View File

@ -106,12 +106,18 @@ http {
location / {
auth_request /auth_verify;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header X-Forwarded-User $user;
auth_request_set $user $upstream_http_remote_user;
proxy_set_header Remote-User $user;
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $groups;
auth_request_set $name $upstream_http_remote_name;
proxy_set_header Remote-Name $name;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-Email $email;
# Route the request to the correct virtual host in the backend.
proxy_set_header Host $http_host;
@ -172,6 +178,12 @@ http {
auth_request_set $groups $upstream_http_remote_groups;
proxy_set_header Remote-Groups $groups;
auth_request_set $name $upstream_http_remote_name;
proxy_set_header Remote-Name $name;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-Email $email;
set $target_url $scheme://$http_host$request_uri;
error_page 401 =302 https://login.example.com:8080/?rd=$target_url;

View File

@ -12,7 +12,7 @@ services:
- 'traefik.http.middlewares.authelia.forwardauth.address=https://authelia-backend:9091${PathPrefix}/api/verify?rd=https://login.example.com:8080${PathPrefix}' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.tls.insecureSkipVerify=true' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.trustForwardHeader=true' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups' # Traefik 2.x
- 'traefik.http.middlewares.authelia.forwardauth.authResponseHeaders=Remote-User, Remote-Groups, Remote-Name, Remote-Email' # Traefik 2.x
command:
- '--api'
- '--providers.docker=true'

View File

@ -66,10 +66,14 @@ func (s *CustomHeadersScenario) TestShouldNotForwardCustomHeaderForUnauthenticat
s.Assert().NoError(err)
s.Assert().NotContains(b, "john")
s.Assert().NotContains(b, "admins")
s.Assert().NotContains(b, "John Doe")
s.Assert().NotContains(b, "john.doe@authelia.com")
}
type Headers struct {
ForwardedEmail string `json:"Remote-Email"`
ForwardedGroups string `json:"Remote-Groups"`
ForwardedName string `json:"Remote-Name"`
ForwardedUser string `json:"Remote-User"`
}
@ -113,7 +117,8 @@ func (s *CustomHeadersScenario) TestShouldForwardCustomHeaderForAuthenticatedUse
actualGroups.Add(group)
}
return strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups), nil
return strings.Contains(payload.Headers.ForwardedUser, "john") && expectedGroups.Equal(actualGroups) &&
strings.Contains(payload.Headers.ForwardedName, "John Doe") && strings.Contains(payload.Headers.ForwardedEmail, "john.doe@authelia.com"), nil
})
require.NoError(s.T(), err)