server icon indicating copy to clipboard operation
server copied to clipboard

SSO: Invalid redirect URI when using Azure OIDC or SAML 2.0

Open ehumphrey-payments opened this issue 3 years ago • 8 comments

Steps To Reproduce

  1. Start up Bitwarden from image: bitwarden/self-host:dev with SSO enabled.
    (The same issue was also encountered using the beta tag.) The internal port of the Bitwarden container is 8080.
    An Nginx termination proxy is in front of it; you can access the Bitwarden web client at https://bitwarden.domain.com (i.e., port 443).
  2. Create an organization in Bitwarden and activate an Enterprise licence.
  3. Follow Azure OIDC Implementation or Azure SAML Implementation to set up OIDC or SAML 2.0 with Microsoft Azure.
  4. Sign in from your Bitwarden web client using enterprise single sign-on (e.g., at https://bitwarden.domain.com/#/sso).
  5. Before seeing any Microsoft login screen, you will see an error page similar to:

    Error: invalid_request

    There was an unexpected error during single sign-on. Request ID: 0HMMUM26RJOQ9:00000002

The same error will have appeared regardless of whether you used SAML or OIDC.
Redirect behaviour and binding type also do not matter.

Expected Result

Enterprise sign-in with Azure OIDC / Azure SAML 2.0 works as expected, bringing you to a Microsoft AAD log-in screen and signing you into the Bitwarden server.

Actual Result

Enterprise sign-in with Azure OIDC / Azure SAML 2.0 fails. Before seeing a Microsoft AAD login screen, you see the above error message at https://bitwarden.domain.com/sso/Error?errorId=REDACTED

Screenshots or Videos

Screenshot 2022-12-15 at 10 56 39 AM
Screenshot 2022-12-15 at 10 54 37 AM

Additional Context

~~I tried contacting Bitwarden's technical support team regarding this, but the confirmation email never showed up.~~ Nvm; the site says there's a confirmation email but it goes through anyway without one.

These requests appear in the Nginx server logs after clicking the "Log in" button shown above.

"GET /identity/account/prevalidate?domainHint=foo HTTP/1.1" 200 328 "https://bitwarden.domain.com/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36" "-"
"GET /identity/connect/authorize?client_id=web&redirect_uri=https%3A%2F%2Fbitwarden.domain.com%2Fsso-connector.html&response_type=code&scope=api%20offline_access&state=REDACTEDx_identifier=foo&code_challenge=REDACTED&code_challenge_method=S256&response_mode=query&domain_hint=foo&ssoToken=BWUserPrefix_REDACTED HTTP/1.1" 302 0 "https://bitwarden.domain.com/"
"GET /identity/Account/Login?ReturnUrl=%2Fidentity%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3Dweb%26redirect_uri%3Dhttps%253A%252F%252Fbitwarden.domain.com%252Fsso-connector.html%26response_type%3Dcode%26scope%3Dapi%2520offline_access%26state%3DREDACTEDx_identifier%253Dfoo%26code_challenge%3DREDACTED%26code_challenge_method%3DS256%26response_mode%3Dquery%26domain_hint%3Dfoo%26ssoToken%3DBWUserPrefix_REDACTED HTTP/1.1" 302 0 "-"
"GET /identity/account/ExternalChallenge?domainHint=foo&returnUrl=%2Fidentity%2Fconnect%2Fauthorize%2Fcallback%3Fclient_id%3Dweb%26redirect_uri%3Dhttps%253A%252F%252Fbitwarden.domain.com%252Fsso-connector.html%26response_type%3Dcode%26scope%3Dapi%2520offline_access%26state%3DREDACTEDx_identifier%253Dfoo%26code_challenge%3DREDACTED%26code_challenge_method%3DS256%26response_mode%3Dquery%26domain_hint%3Dfoo%26ssoToken%3DBWUserPrefix_REDACTED&ssoToken=BWUserPrefix_REDACTED HTTP/1.1" 302 0 "-"
"GET /sso/connect/authorize?client_id=oidc-identity&redirect_uri=http%3A%2F%2Fbitwarden.domain.com%2Fidentity%2Fsignin-oidc&response_type=code&scope=openid%20profile&code_challenge=REDACTED&code_challenge_method=S256&response_mode=form_post&nonce=REDACTED&domain_hint=foo&organizationId=REDACTED&ssoToken=BWUserPrefix_REDACTED&state=REDACTED&x-client-SKU=ID_NETSTANDARD2_0&x-client-ver=6.10.0.0 HTTP/1.1" 302 0 "-"
"GET /sso/Error?errorId=REDACTED HTTP/1.1" 200 764 "-"

I'm not really sure what the issue is here. At one point it's using the HTTPS URI and at another point it's using the HTTPS URI, but it's a 302 so maybe it's redirecting correctly. The URL encoded characters could also be causing issues? Not sure. In Azure, my redirect URL is https://bitwarden.domain.com/sso/oidc-signin, with HTTPS. I've taken a look at the code in bitwarden/clients, but I'm not sure what I'm doing wrong.


  • Every other feature seems to work without issue.
  • BW_ENABLE_SSO is set to "true" and the organization has an active Bitwarden Enterprise licence.
  • My BW_DOMAIN is of the format: bitwarden.domain.com
  • For OIDC, my redirect URL is the same that automatically appears when setting up OIDC through the web client: https://bitwarden.domain.com/sso/oidc-signin

Please let me know if this issue may not be unique to Bitwarden Unified and is instead an issue with bitwarden/clients or my configuration.

Githash Version

7cbc4a89-dirty

Environment Details

  • Operating System: Ubuntu 22.04
  • Environment: Docker
  • Hardware: Intel(R) Xeon(R) CPU E5-2673 v3 @ 2.40GHz, 4 GiB RAM (Standard_B2s Azure virtual machine)
  • Bitwarden behind Nginx TLS termination proxy

Database Image

postgres:14.6-alpine

Issue-Link

https://github.com/bitwarden/server/issues/2480

Issue Tracking Info

  • [X] I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.

ehumphrey-payments avatar Dec 15 '22 16:12 ehumphrey-payments

I think I got the same error like this before, the internal redirection from identity to sso is wrongly configured as by default is sso:5000. Since I deployed the application in Kubernetes. I changed that value to Kubernetes service address at environment variable globalSettings__baseServiceUri__internalSso.

In your case it might be need to change to localhost? since you using the unified image

Rezkmike avatar Dec 15 '22 16:12 Rezkmike

Hmm, I could see it being something like that that. I tried setting globalSettings__baseServiceUri__internalSso to localhost and the sso module errored out and stopped. Does the unified container support all the same environment variables as the regular deployment? I didn't see that one listed.

I also tried setting it to: bitwarden (the service/container name), with ports, etc.; the sso service always crashes and stops.

Is there any other way I can change the redirect URI on the application side? Can't/shouldn't change BW_DOMAIN, of course. Could be a red herring too?

ehumphrey-payments avatar Dec 15 '22 17:12 ehumphrey-payments

@ErikHumphrey Any errors logged from the SSO or Identity application under /etc/bitwarden/logs?

kspearrin avatar Dec 15 '22 18:12 kspearrin

@kspearrin identity.log has nothing for this one. Here's what sso.log has:

2022-12-15 18:44:51.616 +00:00 [ERR] Invalid redirect_uri: http://bitwarden.dev.rcgtconsulting.io/identity/signin-oidc
{"ClientId":"oidc-identity","ClientName":null,"RedirectUri":null,"AllowedRedirectUris":["https://bitwarden.domain.com/identity/signin-oidc"],"SubjectId":"anonymous","ResponseType":null,"ResponseMode":null,"GrantType":null,"RequestedScopes":"","State":null,"UiLocales":null,"Nonce":null,"AuthenticationContextReferenceClasses":null,"DisplayMode":null,"PromptMode":"","MaxAge":null,"LoginHint":null,"SessionId":null,"Raw":{"client_id":"oidc-identity","redirect_uri":"http://bitwarden.domain.com/identity/signin-oidc","response_type":"code","scope":"openid profile","code_challenge":"redacted","code_challenge_method":"S256","response_mode":"form_post","nonce":"redacted","domain_hint":"foo","organizationId":"redacted","ssoToken":"BWUserPrefix_redacted","state":"redacted","x-client-SKU":"ID_NETSTANDARD2_0","x-client-ver":"6.10.0.0"},"$type":"AuthorizeRequestValidationLog"}
2022-12-15 18:44:51.616 +00:00 [ERR] Request validation failed

Looks like the redirect_uri it's using with http:// doesn't match the only AllowedRedirectUris which uses https://.

Edit: Thanks for the replies; I'll investigate this more next week or two.

ehumphrey-payments avatar Dec 15 '22 18:12 ehumphrey-payments

Hmm, we need to figure out how to get Identity to redirect to SSO with https. I imagine this is happening because somewhere is not respecting your proxied protocol. Maybe something can be set in your nginx proxy to properly forward the protocol? I am not an expert with Nginx.

kspearrin avatar Dec 15 '22 18:12 kspearrin

@ErikHumphrey , After I checked in the container, its whether like the SSO not enable by default (need to add sso env enable yes or something) or just the NGINX routing is not there. If you go to /etc/nginx/http.d/bitwarden.conf there is no SSO location configured, you may need to add the sso block inside this file.

Rezkmike avatar Dec 16 '22 03:12 Rezkmike

@Rezkmike you just need to set BW_ENABLE_SSO to true to get that going. @ErikHumphrey mentioned that he already did that.

kspearrin avatar Dec 16 '22 03:12 kspearrin

Wasn't able to pull off anything with the Nginx configuration, I don't see any issues there. It likely needs a change on the Bitwarden end & is web server/proxy-agnostic. Assuming it's unique to Unified, I'll try the main deployment in the meantime.

ehumphrey-payments avatar Dec 20 '22 21:12 ehumphrey-payments

I was having the same issue and was able to resolve it by:

  1. Enabling SSL
  2. Ensuring my load balancer was sending traffic to the SSL port

It would be nice to not need to enable SSL on the container when my load balancer is already doing SSL termination.


I also had to edit /etc/nginx/http.d/bitwarden.conf and add a / after sso

location /sso/ {
    proxy_pass http://localhost:5007;
    include /etc/nginx/security-headers-ssl.conf;
    include /etc/nginx/security-headers.conf;
    add_header X-Frame-Options SAMEORIGIN;
  }

Otherwise, the sso-connector.html file didn't load properly.

sniper7kills avatar Jan 17 '23 00:01 sniper7kills

@sniper7kills Although enabling SSL did work, but some of us are hosting Bitwarden behind an IIS proxy, and IIS does not support SSL passthrough.

We also had leveraged our IIS infrastructure as our SSL offloading proxy.

We tried disabling SSL on unified and set X-Forwarded-Proto but it still doesn't help. The redirect URL generated by Bitwarden is still http. We tried to set redirect URL in Azure AD to also allow http, but that is not allowed because Azure AD required https for redirect URIs unless it is localhost

wizpresso-steve-cy-fan avatar Jan 19 '23 08:01 wizpresso-steve-cy-fan

I have reproduced this and traced the issue back to our proxy.conf file here: https://github.com/bitwarden/server/blob/master/docker-unified/nginx/proxy.conf#L6

Setting the following values manually to https solves the issue:

proxy_set_header        X-Url-Scheme      https;
proxy_set_header        X-Forwarded-Proto https;

This is because in order to properly handle redirects, ASP.NET Core requires that X-Forwarded-Proto be properly set. See docs here: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-7.0

We already do all of this. The problem seems to manifest itself whenever using more than one proxy server. Internally, the Bitwarden unified container is already running a nginx proxy and is directing traffic to various ASP.NET Core applications running at http://localhost:5xxx. In front of that, I have another nginx proxy that terminates SSL and is directing traffic from the public host to the unified container. So you end up with a chain that looks something like this:

1. https://bitwarden.mydomain.com/identity (public nginx proxy)
2.    => http://bitwarden:8080/identity (unified nginx proxy)
3.          => http://localhost:5005 (ASP.NET Core Identity application)

With this chain, you can see the $scheme that is forwarded from step 2 to 3 is http, which is what ASP.NET Core then interprets as the protocol of the running application and is used for these SSO redirects. This is also why things start working when configuring BW_ENABLE_SSL=true, because you are changing the protocol from http => https in step 2 and $scheme now matches the upstream public $scheme.

As I mentioned earlier, you could just hardcode the https value rather than using the upstream $scheme. I don't particularly like this solution since it's not very dynamic.

I am not a nginx expert, but does anyone know of a way to handle this dynamically? We need some way to see what the original upstream host and protocol is to properly pass through the chain of proxies.

kspearrin avatar Apr 11 '23 20:04 kspearrin

I'm not an Nginx expert either and can't test this at the moment, but would work if we set X-Forwarded-Proto to the upstream public Nginx proxy value (if set) and $scheme otherwise?

Upstream public proxy:

proxy_set_header X-Forwarded-Proto $scheme;

Bitwarden Unified proxy:

proxy_set_header X-Forwarded-Proto $scheme; # Normally empty by default, but set upstream

That could cause issues if you don't have an upstream proxy as it needs to be set, as you said. So something like:

proxy_set_header X-Forwarded-Proto $scheme;
if ($http_x_forwarded_proto) {
  proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
}

or something similar to that effect:

set $forwarded_proto $http_x_forwarded_proto;
if ($forwarded_proto = '') {
    set $forwarded_proto $scheme;
}
proxy_set_header X-Forwarded-Proto $forwarded_proto;

ehumphrey-payments avatar Apr 11 '23 21:04 ehumphrey-payments

I think I got this working with something like

map $http_x_forwarded_proto $proto_scheme {
    default "$scheme";
    ~. "$http_x_forwarded_proto";  # Regular expression to match any value
}

proxy_set_header        X-Url-Scheme      $proto_scheme;
proxy_set_header        X-Forwarded-Proto $proto_scheme;

This null coalesces the upstream X-Forwarded-Proto header and defaults back to $scheme if not set.

kspearrin avatar Apr 11 '23 21:04 kspearrin

#2847 should resolve the issue. Feel free to try the dev tag from DockerHub for now if you want to confirm.

kspearrin avatar Apr 13 '23 14:04 kspearrin