oauth2-proxy: Not redirecting to subpath after login using Traefik's 401 errors middleware

Expected Behavior

  1. Attempt to access service on subpath example.domain/sub/path
  2. Get redirected to oauth2-proxy on example.domain/oauth2/auth
  3. Log in through auth provider
  4. Get redirected to example.domain/sub/path

Current Behavior

  1. Attempt to access service on subpath example.domain/sub/path
  2. Get redirected to oauth2-proxy on example.domain/oauth2/auth
  3. Log in through auth provider
  4. Get redirected to example.domain instead of example.domain/sub/path

Steps to Reproduce (for bugs)

  1. Use following config values for oauth2-proxy
  oauth2_proxy.cfg: |-
    provider = "oidc"
    provider_display_name = "Keycloak"
    redirect_url = "https://example.domain/oauth2/callback"
    oidc_issuer_url = "https://example.domain/auth/"
    cookie_secure = false
    reverse_proxy = true
    email_domains = [ "*" ]
    upstreams = [ "file:///dev/null" ]
    pass_access_token = true
    pass_authorization_header = true
    set_authorization_header = true
    set_xauthrequest = true
    pass_user_headers = true
    whitelist_domains= [ ".example.domain" ]
  1. Set up traefik middlewares and ingresses as currently described in the docs: https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview#forwardauth-with-401-errors-middleware In kubernetes CRD form:
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: auth-headers
spec:
  headers:
    sslRedirect: true
    stsSeconds: 315360000
    browserXssFilter: true
    contentTypeNosniff: true
    forceSTSHeader: true
    stsIncludeSubdomains: true
    stsPreload: true
    frameDeny: true
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: oauth-auth
spec:
  forwardAuth:
    address: https://example.domain/oauth2/auth
    trustForwardHeader: true
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: oauth-errors
spec:
  errors:
    status:
      - "401-403"
    service:
      name: oauth2-proxy
      port: 80
    query: "/oauth2/sign_in"
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: oauth2-proxy
spec:
  entryPoints:
    - websecure
  routes:
  - match: PathPrefix(`/oauth2/`)
    kind: Rule
    services:
    - name: oauth2-proxy
      port: 80
    middlewares:
    - name: auth-headers
  1. Expose a service
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: subpath-test
spec:
  entryPoints:
  - websecure
  routes:
  - match: PathPrefix(`/foo/bar`)
    kind: Rule
    services:
    - name: bar
      port: 8080
    middlewares:
    - name: oauth-errors
      namespace: traefik-v2
    - name: oauth-auth
      namespace: traefik-v2
  1. Navigate to example.domain/foo/bar, follow login flow

Context

There seem to be quite a few issues related to Oauth2 Proxy and Traefik, however the closest I could find to my current issue is https://github.com/oauth2-proxy/oauth2-proxy/issues/397 which is over a year old. Besides the environment traefik is running in, the issue is very similar. https://github.com/oauth2-proxy/oauth2-proxy/issues/1255 seems somewhat similar as well, but I’m currently not using skip-provider-button.

As there are several issues related to Traefik + Oauth2 Proxy both here and on Traefik’s github page, I might have missed potential solutions, but from what I’ve read so far it seems like the only workaround at the moment is to create a middleware that attaches the X-Auth-Request-Redirect header manually. As Traefik is currently not offering dynamic headers, this has to be done per service. Both https://github.com/oauth2-proxy/oauth2-proxy/issues/397 and https://github.com/oauth2-proxy/oauth2-proxy/issues/46#issuecomment-687155032 seem to use custom middlewares for each service to solve the issue, which is far from ideal. https://github.com/traefik/traefik/issues/6839 might eventually provide a generic solution from Traefik’s site, although I’m not completely sure if this is a surefire fix.

An attempt was made at providing the correct functionality from Traefik’s side: https://github.com/traefik/traefik/pull/6835 however developers did not like the oauth2-proxy specific approach.

I’ve posted this here as oauth2-proxy seems more inclined to adopt Traefik specific fixes than the other way around.

Your Environment

  • Version used: oauth-proxy2 v7.1.3 traefik v2.4.9 kubernetes v1.20

Multiple services are hosted on the same domain, but under different subpaths.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 6
  • Comments: 43 (11 by maintainers)

Most upvoted comments

My team ran into a similar issue and tried solutions from here. I’m leaving my solution here for others who end up here too:

  1. Instead of calling any of the /oauth2/* endpoints we just use / for forward auth. As the documentation states:

    All other endpoints will be proxied upstream when authenticated.

  2. Now define an upstream that returns 2xx with --upstream static://204.
  3. Finally enable --skip-provider-button.

Now traefik will do forward auth against / and when the user is unauthenticated we get an immediate redirect. If the user is authenticated the proxy responds with 204 and traefik will call the real service for the site.

This is kinda obvious if you read the documentation carefully. The oauth2/auth is really only suitable for nginx and I think this how my team ended up here.

Hope that helps.

I am using the hack above for now, i.e using a custom template for sign_in where I set rd=window.location. I copied the template files from https://github.com/oauth2-proxy/oauth2-proxy/tree/master/pkg/app/pagewriter, configure them with

      - "OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR=/templates"

and just added

(function() {
  var inputs = document.getElementsByName('rd');
  for (var i = 0; i < inputs.length; i++)
    inputs[i].value = window.location;
})();

You need to correctly configure WHITELIST_DOMAINS as well. This makes all my subdomains work with the same oauth2-proxy callback.

I’m haven’t taken the time to figure out how to set the Redirect parameter correctly, but hopefully someone will shed the light on that eventually 😃

I’ve posted the issue over at Traefik’s forum, but looking at the lack of response on similar issues over there, I don’t exactly have high hopes for a meaningful reply.

Earlier in the thread you said the following:

So looks to me like, whatever is injecting “/oauth2/start?rd=https%3A%2F%2Fexample.com%2F” is not injecting the path. Looking at the rd value here, you are only setting the redirect to the root of the domain (%2F being /). So you need to remove that rd parameter to allow the traefik headers to take precedence in the redirect fetching logic.

Otherwise, you need to update whatever is injecting the rd paremeter to also include the path as well, how you’ll do that I’m not sure though.

Do you have any clue on which component provides the rd parameter, i.e. is it Traefik, Oauth2-proxy, or something entirely different?

In the logs from my working traefik-forward-auth setup, a proxy auth component created specifically for Traefik, I can see my original url being sent as part of the state from the forward auth component to Keycloak (our OIDC provider) and then again in the callback. These are the logs for a request to sub.domain.com/monitoring/prometheus/graph:

time="2021-10-20T13:13:31Z" level=debug msg="Set CSRF cookie and redirected to provider login url" csrf_cookie="_forward_auth_csrf=6f6dc18df84a100c97674769e50ab040; Path=/; Domain=sub.domain.com; Expires=Thu, 21 Oct 2021 01:13:31 GMT; HttpOnly; Secure" handler=Auth host=sub.domain.com login_url="https://sub.domain.com/auth/realms/main/protocol/openid-connect/auth?client_id=general-forward-auth&redirect_uri=https%3A%2F%2Fsub.domain.com%2F_oauth&response_type=code&scope=openid+profile+email&state=6f6dc18df84a100c97674769e50ab040%3Aoidc%3Ahttps%3A%2F%2Fsub.domain.com%2Fmonitoring%2Fprometheus%2Fgraph" method=GET proto=https rule=default source_ip=10.244.5.0 uri=/monitoring/prometheus/graph
time="2021-10-20T13:22:16Z" level=debug msg="Handling callback" cookies="[_forward_auth_csrf=6f6dc18df84a100c97674769e50ab040]" handler=AuthCallback host=sub.domain.com method=GET proto=https rule=default source_ip=10.244.2.1 uri="/_oauth?state=6f6dc18df84a100c97674769e50ab040%3Aoidc%3Ahttps%3A%2F%2Fsub.domain.com%2Fmonitoring%2Fprometheus%2Fgraph&session_state=6ffd61cf-d29e-4adb-a81d-58c075c65b65&code=0957b9ed-5ebd-43b4-ade8-4153ff5b6f10.6ffd61cf-d29e-4adb-a81d-58c075c65b65.176da2fa-65b4-4171-bf2e-22a4a44a2860"
time="2021-10-20T13:22:17Z" level=info msg="Successfully generated auth cookie, redirecting user." handler=AuthCallback host=sub.domain.com method=GET proto=https provider=oidc redirect="https://sub.domain.com/monitoring/prometheus/graph" rule=default source_ip=10.244.2.1 uri="/_oauth?state=6f6dc18df84a100c97674769e50ab040%3Aoidc%3Ahttps%3A%2F%2Fsub.domain.com%2Fmonitoring%2Fprometheus%2Fgraph&session_state=6ffd61cf-d29e-4adb-a81d-58c075c65b65&code=0957b9ed-5ebd-43b4-ade8-4153ff5b6f10.6ffd61cf-d29e-4adb-a81d-58c075c65b65.176da2fa-65b4-4171-bf2e-22a4a44a2860" user=john@doe.com

On their github page, the following is mentioned:

Supports multiple domains/subdomains by dynamically generating redirect_uri’s

I’m guessing this is what does not work with Oauth2-proxy? Where traefik-forward-auth provides the redirect url themselves, oauth2-proxy expects Traefik to provide the redirect url?

You also said:

You’ve got the rd parameter in there, so it looks like we aren’t even using the Traefik checks here.

I did a quick search for ‘traefik check’ in the code, but couldn’t find anything relevant. Can you clarify what these checks are or point me in the right direction to find these checks?

Thanks for all the help you’ve provided so far.

I am using the hack above for now, i.e using a custom template for sign_in where I set rd=window.location. I copied the template files from https://github.com/oauth2-proxy/oauth2-proxy/tree/master/pkg/app/pagewriter, configure them with

@jonananas could you kindly explain where exactly you added this code in sign_in.html? I’ve got zero front-end-dev-experience

(function() {
  var inputs = document.getElementsByName('rd');
  for (var i = 0; i < inputs.length; i++)
    inputs[i].value = window.location;
})();

Sure, have a look at https://github.com/jonananas/traefik-oauth2-proxy/blob/7f6327ef23ac3be222d2b2516baf271efb9f35b3/oauth_templates/sign_in.html#L69.

This hack could be done in the OAUTH2_PROXY_FOOTER environnement variable like :

OAUTH2_PROXY_FOOTER = "<script>(function(){var rd=document.getElementsByName('rd');for(var i=0;i<rd.length;i++)rd[i].value=window.location.toString().split('/oauth2')[0]})()</script>"

The project refers to OAuth2 Proxy yes. This project has been a passion project for a long time, we still want to maintain it and provide guidance where we can but we can only do so when our schedules permit it. Typically my evenings and weekends I try to spend some time on the project but I can’t as often as I would like and I don’t get to do much on the code side anymore either.

I’m not sure about alternatives no. The real solution is to bring in some new maintainers but finding people willing and able to be a maintainer is difficult.

@JoelSpeed please excuse my slightly off-topic question, but does “the project” refer to oauth2-proxy in general? If the maintainers don’t use it anymore, is there a recommended alternative?

+1 for this. I’ve got auth working fine with static upstream configuration without errors middleware, but problem is that I’m trying to add allowed_groups to /oauth2/auth endpoint for forwardauth middleware. IIUC this can’t work without redirects/errors middleware, as oauth2-proxy will just return 401 for all requests. Does anyone have any ideas how to embedd allowed_groups query param into forwardauth middleware with that static upstreams config?

Unfortunately I wasn’t able to get auth working at all with errors middleware and querying /oauth2/start or /oauth2/sign_in on 401-403. I’m getting empty page with “Found” link and CSRF token mismatch error after I press that.

Does anyone got working setup for error middleware? Any help will be appretiated.

Hi @lanmarti,

Below my Middleware / IngressRoute Configuration. Don’t forger i use the ForwardAuth with static upstreams configuration not this one ForwardAuth with 401 errors middleware

---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: auth-headers
spec:
  headers:
    sslRedirect: true
    stsSeconds: 315360000
    browserXssFilter: true
    contentTypeNosniff: true
    forceSTSHeader: true
    sslHost: mydomain.com
    stsIncludeSubdomains: true
    stsPreload: true
    frameDeny: true
---
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: oauth2-auth-redirect
  namespace: traefik-system
spec:
  forwardAuth:
    address: 'https://auth.mydomain.com/'
    trustForwardHeader: true
    authResponseHeaders:
      - X-Auth-Request-Access-Token
      - Authorization
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: oauth2-proxy
spec:
  entryPoints:
    - websecure
  routes:
    - match: "Host(`auth.mydomain.com`) && PathPrefix(`/oauth2/`)"
      kind: Rule
      services:
        - kind: Service
          name: oauth2-proxy
          port: 80
      middlewares:
        - name: auth-headers
  tls:
    secretName: wildcard-mydomain.com-cert-tls
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: oauth2-proxy-redirect-app
spec:
  entryPoints:
    - websecure
  routes:
    - match: "Host(`traefik.mydomain.com`, `myapp.mydomain.com`) && PathPrefix(`/oauth2/`)"
      kind: Rule
      services:
        - kind: Service
          name: oauth2-proxy
          port: 80
      middlewares:
        - name: auth-headers
  tls:
    secretName: wildcard-mydomain.com-cert-tls
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: dashboard
spec:
  entryPoints:
  - web
  - websecure
  routes:
  - kind: Rule
    match: Host(`traefik.mydomain.com`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
    services:
    - kind: TraefikService
      name: api@internal
    middlewares:
       - name: oauth2-auth-redirect
  tls:
    secretName: wildcard-mydomain.com-cert-tls

The hack from @jonananas (Thanks !) is still required to make this work.

Hi, after further investigation, i found a “missing” part on my Traefik configuration. In fact i try to find oauth2 config to redirect to my website after login but no way always redirect behind oauth2 home page. After apply this paramerter in my traefik config it’s finaly work’s.

Indeed the problem is not on oauth2 config but if the forwarded-headers in traefik is not activated i will not fowarde the X-Fowarded-URI that permit to oauth2 to rediredt to your website after authentification.

I use that config for oauth2-proxy oauth2-proxy.cfg : email_domains = [ “*” ] upstreams = [ “static://202” ] reverse_proxy = “true” redirect_url = “https://mydomain/oauth2/callback” pass_access_token = “true” pass_authorization_header = “true” set_authorization_header = “true” set_xauthrequest = “true” cookie_domains = “.mydomain” whitelist_domains = “.mydomain” skip_provider_button = “true” cookie_secure = “true”

Traefik config (command) : –entryPoints.web.forwardedHeaders.insecure –entryPoints.websecure.forwardedHeaders.insecure

I hope that thing permit to help.

@xoxys Had a similar idea. One issue is that you lose any information about where to redirect after the login succeeds. If you use oauth2-proxy with multiple subdomains this might not be desired. What worked for me: Use a custom template (as described in #1507) and instead of adding a meta redirect, use javascript to set your current url as redirect target (“rd”) and submit the form automatically. This assumes that your url remains unchanged.

This is an ugly hack but I currently don’t see any better way. It seems to me that as long as traefik doesn’t allow changing the status code from 401 to 302 with a custom middleware any redirects coming from oauth2-proxy will have no effect.

I am using the hack above for now, i.e using a custom template for sign_in where I set rd=window.location. I copied the template files from https://github.com/oauth2-proxy/oauth2-proxy/tree/master/pkg/app/pagewriter, configure them with

@jonananas could you kindly explain where exactly you added this code in sign_in.html? I’ve got zero front-end-dev-experience

(function() {
  var inputs = document.getElementsByName('rd');
  for (var i = 0; i < inputs.length; i++)
    inputs[i].value = window.location;
})();

Sure, have a look at https://github.com/jonananas/traefik-oauth2-proxy/blob/7f6327ef23ac3be222d2b2516baf271efb9f35b3/oauth_templates/sign_in.html#L69.

Hello, @croneter if it helps, this is how I am using the sign_in.hml trick using docker-compose: (PS: a more complete example is here, but it dates from a few month back https://gist.github.com/copolycube/dc6a52812fea19361f5aa5b0f3d514dd )

  traefik:
    image: traefik:latest
    container_name: "traefik"
    command:
      - --log.level=DEBUG
      - --api.dashboard=true
      [...]<depends on your needs....[...]
    networks:
      - internal
      - docker-proxy-internal

    labels:
      [...]<depends on your needs....[...]
      traefik.http.middlewares.oauth-chain.chain.middlewares: default-https,oauth-signin,oauth-verify

# https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/oauth_provider#gitlab-auth-provider
# https://alex.thom.ae/2020/01/12/deploy-traefik-prometheus-grafana-oauth2_proxy-docker-compose/
  oauth:
          #image: quay.io/pusher/oauth2_proxy:${OAUTH2_PROXY_VERSION}
    image: quay.io/oauth2-proxy/oauth2-proxy:v7.2.1
    container_name: "oauth"
    restart: always
    healthcheck:
      test: ["CMD", "wget", "--tries=1", "--spider", "http://oauth:4180/ping"]
      interval: 60s
      timeout: 10s
    labels:
      ai.ix.expose: 'true'
      traefik.enable: 'true'
      traefik.http.middlewares.oauth-verify.forwardAuth.address: http://oauth:4180/oauth2/auth
      traefik.http.middlewares.oauth-verify.forwardAuth.trustForwardHeader: 'true'
      traefik.http.middlewares.oauth-verify.forwardAuth.authResponseHeaders: "X-Auth-Request-User,X-Auth-Request-Email,Set-Cookie,X-Auth-Request-Access-Token,Authorization,X-Auth-Request-Redirect"
      traefik.http.middlewares.oauth-signin.errors.service: oauth@docker
      traefik.http.middlewares.oauth-signin.errors.status: '401'
      traefik.http.middlewares.oauth-signin.errors.query: /oauth2/sign_in
      traefik.http.routers.oauth.entrypoints: websecure
      traefik.http.routers.oauth.rule: Host(`oauth.${DOMAIN?err}`) || PathPrefix(`/oauth2`)
      traefik.http.routers.oauth.tls.certResolver: myresolver
      traefik.http.routers.oauth.service: oauth@docker
      traefik.http.services.oauth.loadbalancer.server.port: '4180'
    volumes:
      - "./oauth_templates:/templates"
    environment:
            # https://oauth2-proxy.github.io/oauth2-proxy/docs/configuration/overview/#environment-variables
      OAUTH2_PROXY_PROVIDER: gitlab
      OAUTH2_PROXY_EMAIL_DOMAINS: '*'
      OAUTH2_PROXY_GITLAB_GROUP: "${OAUTH2_PROXY_GITLAB_GROUP}"
      OAUTH2_PROXY_CLIENT_ID: "${OAUTH2_PROXY_CLIENT_ID?err}"
      OAUTH2_PROXY_CLIENT_SECRET: "${OAUTH2_PROXY_CLIENT_SECRET?err}"
      OAUTH2_PROXY_COOKIE_DOMAINS: "${DOMAIN?err}"
      OAUTH2_PROXY_COOKIE_REFRESH: '1h'
      OAUTH2_PROXY_COOKIE_SECURE: 'true'
      OAUTH2_PROXY_COOKIE_SECRET: "${OAUTH2_PROXY_COOKIE_SECRET?err}"
      OAUTH2_PROXY_FOOTER: '-'
      OAUTH2_PROXY_HTTP_ADDRESS: '0.0.0.0:4180'
      OAUTH2_PROXY_PASS_BASIC_AUTH: 'false'
      OAUTH2_PROXY_PASS_USER_HEADERS: 'true'
      OAUTH2_PROXY_REVERSE_PROXY: 'true'
      OAUTH2_PROXY_SET_AUTHORIZATION_HEADER: 'true'
      OAUTH2_PROXY_SET_XAUTHREQUEST: 'true'
      OAUTH2_PROXY_WHITELIST_DOMAIN: '.${DOMAIN?err}'
      OAUTH2_PROXY_WHITELIST_DOMAINS: '.${DOMAIN?err}'
      OAUTH2_PROXY_REDIRECT_URL: "https://oauth.${DOMAIN?err}/oauth2/callback"
      OAUTH2_PROXY_INSECURE_OIDC_SKIP_NONCE: 'false'
      # Customize login page - next four
      OAUTH2_PROXY_CUSTOM_TEMPLATES_DIR: "/templates"
      OAUTH2_PROXY_CUSTOM_SIGN_IN_LOGO: "/templates/logo.png"
      OAUTH2_PROXY_BANNER: "Welcome to this server ${DOMAIN?err}"
      OAUTH2_PROXY_FOOTER: "-"
    networks:
      - internal
      - docker-proxy-internal

Please reopen this issue, dear @JoelSpeed, it’s still a hassle if not outright impossible to use the traefik errors middleware - which is necessary if you want to control access per subdomain by allowed_groups

+1 that this be reopened as an active issue. This still hasn’t been resolved in the over 3 years since issues with traefik were first reported.

I replaced /oauth2/sign_in with /oauth2/start in the oauth-errors middleware. From the user’s point of view, nothing has changed, except that instead of a graphic and a login button, there now is a ‘Found’ url that redirects to our Keycloak when clicked, like the button used to do.

The logs are slightly different now, there is no rd parameter present anymore, but redirecting after login still does not work.

10.244.2.0 - 84b5fc04-0c37-49b7-b831-5e954493578a - - [2021/10/05 06:54:53] example.com GET - "/oauth2/auth" HTTP/1.1 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0" 401 13 0.000
[2021/10/05 06:54:53] [stored_session.go:77] Error loading cookied session: cookie "_oauth2_proxy" not present, removing session
10.244.1.0 - a4e28fba-aa13-4d78-a18f-b66f62d7af09 - - [2021/10/05 06:54:53] example.com GET - "/oauth2/start" HTTP/1.1 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0" 302 360 0.000
10.244.1.0 - 52d06dc8-07f1-4397-9814-68e74a1d7e49 - john@doe.com [2021/10/05 06:55:33] [AuthSuccess] Authenticated via OAuth2: Session{email:john@doe.com user:cc6bcc9d-aaab-410c-aa76-3542eedecdb5 PreferredUsername:john@doe.com token:true id_token:true created:2021-10-05 06:55:33.728152323 +0000 UTC m=+80583.709032201 expires:2021-10-05 07:00:33.727315944 +0000 UTC m=+80883.708195821 refresh_token:true groups:[/cluster-admin /developer/trusted-developer]}
10.244.1.0 - 52d06dc8-07f1-4397-9814-68e74a1d7e49 - - [2021/10/05 06:55:33] example.com GET - "/oauth2/callback?state=BuIzLuRu9zrkN3myaC-9g7RwcMEGyo8pHh1c2JLjFe4%3Ahttps%3A%2F%2Fexample.com%2F&session_state=37e63c20-d673-40a7-abad-3f5ffa541c2a&code=4487c28d-3536-4b5a-9685-68df7ed95ca1.37e63c20-d673-40a7-abad-3f5ffa541c2a.2b988a94-90d2-4534-8b61-ed08cb057e8a" HTTP/1.1 "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0" 302 50 0.027

I am facing the same issue, a workaround would be to use the headers middleware for each service, which is far away from a good solution.