Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions e2e/tests/keycloak/tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,55 @@ http:
expect(response?.url()).toMatch(/http:\/\/localhost:8000\/realms\/master\/protocol\/openid-connect\/auth.*/);
});

test("access app with unauthorized passthrough", async ({ page }) => {
await configureTraefik(`
http:
services:
whoami:
loadBalancer:
servers:
- url: http://whoami:80

middlewares:
oidc-auth:
plugin:
traefik-oidc-auth:
LogLevel: DEBUG
Provider:
Url: "\${PROVIDER_URL_HTTP}"
ClientId: "\${CLIENT_ID}"
ClientSecret: "\${CLIENT_SECRET}"
UsePkce: false
UnauthorizedPassthrough: true
LoginUri: "/login"
Headers:
- Name: "Authorization"
Value: "{{\`Bearer: {{ .accessToken }}\`}}"

routers:
whoami:
entryPoints: ["web"]
rule: "HostRegexp(\`.+\`)"
service: whoami
middlewares: ["oidc-auth@file"]
`);

// The first test: unauthenticated request should pass through directly to the whoami page.
const unauthResponse = await page.goto("http://localhost:9080/test1");

// Unauthenticated request passes through with a 200 response and no Authorization header attached.
expect(unauthResponse?.status()).toBe(200);
const unauthAuthHeaderExists = await page.locator(`text=Authorization: Bearer: ey`).isVisible();
expect(unauthAuthHeaderExists).toBeFalsy();

// The second test: authenticated user should pass through with the Authorization header set.
await expectGotoOkay(page, "http://localhost:9080/login");
await login(page, "admin", "admin", "http://localhost:9080");

const authHeaderExists = await page.locator(`text=Authorization: Bearer: ey`).isVisible();
expect(authHeaderExists).toBeTruthy();
});

test("external authentication", async ({ page }) => {
await configureTraefik(`
http:
Expand Down
5 changes: 5 additions & 0 deletions src/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func CreateConfig() *config.Config {
AuthorizationHeader: &config.AuthorizationHeaderConfig{},
AuthorizationCookie: &config.AuthorizationCookieConfig{},
UnauthorizedBehavior: "Auto",
UnauthorizedPassthroughBool: false,
Authorization: &config.AuthorizationConfig{
CheckOnEveryRequest: false,
},
Expand Down Expand Up @@ -93,6 +94,10 @@ func New(uctx context.Context, next http.Handler, cfg *config.Config, name strin
cfg.PostLogoutRedirectUri = utils.ExpandEnvironmentVariableString(cfg.PostLogoutRedirectUri)
cfg.CookieNamePrefix = utils.ExpandEnvironmentVariableString(cfg.CookieNamePrefix)
cfg.UnauthorizedBehavior = utils.ExpandEnvironmentVariableString(cfg.UnauthorizedBehavior)
cfg.UnauthorizedPassthroughBool, err = utils.ExpandEnvironmentVariableBoolean(cfg.UnauthorizedPassthrough, cfg.UnauthorizedPassthroughBool)
if err != nil {
return nil, err
}
cfg.BypassAuthenticationRule = utils.ExpandEnvironmentVariableString(cfg.BypassAuthenticationRule)
cfg.Provider.Url = utils.ExpandEnvironmentVariableString(cfg.Provider.Url)
cfg.Provider.ClientId = utils.ExpandEnvironmentVariableString(cfg.Provider.ClientId)
Expand Down
3 changes: 3 additions & 0 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type Config struct {
AuthorizationCookie *AuthorizationCookieConfig `json:"authorization_cookie"`
UnauthorizedBehavior string `json:"unauthorized_behavior"`

UnauthorizedPassthrough string `json:"unauthorized_passthrough"`
UnauthorizedPassthroughBool bool `json:"unauthorized_passthrough_bool"`

Authorization *AuthorizationConfig `json:"authorization"`

Headers []HeaderConfig `json:"headers"`
Expand Down
31 changes: 19 additions & 12 deletions src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,21 +155,21 @@ func (toa *TraefikOidcAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request)
session.IsAuthorized = isAuthorized(toa.logger, toa.Config.Authorization, claims)
}

if !session.IsAuthorized {
if !session.IsAuthorized && !toa.Config.UnauthorizedPassthroughBool {
toa.handleUnauthorized(rw, req)
return
}

// Attach upstream headers
err = toa.attachHeaders(req, session, claims)
if err != nil {
toa.logger.Log(logging.LevelError, "Error while attaching headers: %s", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
} else if session.IsAuthorized {
// Attach upstream headers
err = toa.attachHeaders(req, session, claims)
if err != nil {
toa.logger.Log(logging.LevelError, "Error while attaching headers: %s", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}

if updateSession {
toa.storeSessionAndAttachCookie(session, rw)
if updateSession {
toa.storeSessionAndAttachCookie(session, rw)
}
}

// Forward the request
Expand All @@ -183,6 +183,13 @@ func (toa *TraefikOidcAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request)
// Clear the session cookie
clearChunkedCookie(toa.Config, rw, req, getSessionCookieName(toa.Config))

if toa.Config.UnauthorizedPassthroughBool {
// Forward the request
toa.sanitizeForUpstream(req)
toa.next.ServeHTTP(rw, req)
return
}

toa.handleUnauthenticated(rw, req)
}

Expand Down
1 change: 1 addition & 0 deletions website/docs/getting-started/middleware-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ But: If you're using YAML-files for configuration you can use [traefik's templat
| `AuthorizationHeader` | no | [`AuthorizationHeader`](#authorization-header) | *none* | AuthorizationHeader Configuration. See *AuthorizationHeader* block. |
| `AuthorizationCookie` | no | [`AuthorizationCookie`](#authorization-cookie) | *none* | AuthorizationCookie Configuration. See *AuthorizationCookie* block. |
| `UnauthorizedBehavior`* | no | `string` | `Auto` | Defines the behavior for unauthenticated requests. `Challenge` means the user will be redirected to the IDP's login page, `Unauthorized` will return a 401 status response, and `Auto` will automatically choose based on request type (HTML requests get redirected, AJAX requests get 401). |
| `UnauthorizedPassthrough`* | no | `bool` | `false` | When enabled, unauthenticated and unauthorized requests are forwarded to the upstream service instead of being rejected or redirected. Any configured upstream `Headers` are only attached when the user is authenticated and authorized. |
| `Authorization` | no | [`Authorization`](#authorization) | *none* | Authorization Configuration. See *Authorization* block. |
| `Headers` | no | [`Header`](#header) | *none* | Supplies a list of headers which will be attached to the upstream request. See *Header* block. |
| `BypassAuthenticationRule`* | no | `string` | *none* | Specifies an optional rule to bypass authentication. See [Bypass Authentication Rule](./bypass-authentication-rule.md) for more details. |
Expand Down
1 change: 1 addition & 0 deletions workspaces/configs/http.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ http:
Name: "CustomAuth"
AuthorizationCookie:
Name: "CustomAuth"
#UnauthorizedPassthrough: true
#BypassAuthenticationRule: "Header(`cache-control`, `no-cache`) || HeaderRegexp(`X-Real-Ip`, `^172\\.18\\.`)"
#UnauthorizedBehavior: "Unauthorized"
# Authorization:
Expand Down
Loading