Discourse in Docker + NGINX reverse proxy + SSL everywhere + OAuth2 Custom

(Dave Lane) #1

I’m hoping someone here will have the necessary insight/Discourse debugging fu to help me work out why my SSO efforts are failing…

The story so far: I have a Discourse instance (v1.7.0.beta6), running in a Docker container on Ubuntu Linux 14.04, serving via an HTTPS-configured nginx reverse proxy. I described the configuration previously. The HTTPS is provided by Let’s Encrypt certs. It has been running without issue for months now, using templates/web.template.yml and templates/web.ratelimited.template.yml - so between the nginx reverse proxy on the VM and the Docker container, traffic is going by HTTP, not HTTPS. All external traffic is automatically redirected to external port 443.

Now, I want to allow users to authenticate via an OAuth2 server provided by a multi-site WordPress instance I’m running on the same VM (not sure if this is relevant). It’s got the WP OAuth Server (full version, not gratis one). I’ve set up the client for my Discourse instance, and have configured all the URLs in both the WP Server’s config, i.e. the callback URL, and the various authorisation, token, and user URLs to be HTTPS. I also installed and rebuilt my Discourse instance with the https://github.com/discourse/discourse-oauth2-basic.git plugin.

Initially the OAuth process was failing at the callback URL from Discourse, with an error related to “OpenURI::HTTPError (400 Bad Request) /usr/local/lib/ruby/2.3.0/open-uri.rb:359:in `open_http’” via the logs… it seems as though perhaps it’s attempting to do the HTTPS via HTTP.

So I’ve now tweaked my nginx reverse proxy configuration to include

location @discourse2 {
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto https;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-Host $host:443;
  proxy_set_header X-Forwarded-Port 443;
  proxy_redirect off;
  add_header Front-End-Https on;
  proxy_pass https://discourse2;

and this (discourse2 is the 2nd instance of Discourse running on this VM):

upstream discourse2 {
  server fail_timeout=5;

and added the templates/web.ssl.template.yml to my app.yml, put copies of my Let’s Encrypt certs into the shared/ssl/ dir, configured new ports in apps.yml file - "" and and ran ./launcher rebuild app… The site, amazingly, works with this new configuration, and, with turning on force_https in Discourse’s security settings everything should be SSL end-to-end if I’m not mistaken…

And yet, I still can’t seem to get OAuth2 to work. I continue to get the open_http error, but am unable to work out what is going wrong. I note that the accompanying Request URI (from the logs) shows not just a single path, e.g. /auth/oauth2_basic/callback?code=aw1tb7i0n8zekhrh3i9ldhnzzbhdwzak4arw5dzz&state=d514fc06d5e5a32e1b8cc9c327f88571ff3a2ba83e6836c0 but a whole long list of them, presumably from prior test authorisation attempts, separated by commas. I include the one above, as it’s expired and doesn’t work in any case…

This look familiar to anyone? Is there a way to get this to work without enabling SSL in the Docker containers? For the record, I’ve read up on the thread where people are encountering roughly similar problems with Google’s OAuth2 service… but no major insights… Any thoughts gratefully accepted!

How can I install discourse as forum.example.com with Easyengine, Letsencrypt, Wordpress all together?
(Dave Lane) #2

I also note that since upgrading to “HTTPS everywhere” the upgrade to 1.7.0-beta8 (both my two sites have it pending) does not provide an “upgrade now” link as it does on the admin dashboard of my non-HTTPS instance of Discourse… which is unexpected. Manually entering the /admin/upgrade#/upgrade/discourse path on the HTTPS site does trigger the upgrade interface, which works, for the record. But the link was not provided on the dashboard page.

(Jay Pfaffman) #3

If you want to authenticate against WordPress, why not use the wp-discourse plugin?

(Dave Lane) #4

Fair question - we’re wanting to provide SSO for a whole suite of web services for our users (plus, we’re using WP in multi-site mode which adds authentication complexities), and it seemed that the custom/basic OAuth2 functionality would be able to do that. I believe the only issues aren’t really related to the OAuth functionality (neither the WP OAuth server nor the Discourse client) but rather the specifics of OAuth with enforced HTTPS on the Discourse side (where there’s a problem with the validity/acceptability of the authorization request which differs (HTTPS from the WP server, but was originally being sent HTTP between the reverse proxy and the Discourse Docker container, but should now be HTTPS end-to-end based on the changes I made described above).

Do you think we could use the wp-discourse plugin alongside the WP OAuth2 Server? Open to ideas.

(Maarten Busstra) #5

Have you managed to solve this? I’m in (almost) exactly the same boat. What I’ve noticed is that the request to my user_json_url (website.com/oauth/me?access_token=:token) returns 400 on the Discourse backend, causing a 500 on there.

(Dave Lane) #6

I haven’t solved it yet - focusing on other things until I either gain insight or get a response from someone who has some… Will be keen to document the solution here when we find it!

(Jay Pfaffman) #7

It’s not clear that you need to do anything that the wp-discourse plugin does not do and it seems apparently that the WP OAuth plugin is more complicated and that few people here know how it works.

It would seem to me that you’d have everything talk to the https server even if they are inside the same container and could talk directly, but I mostly don’t understand that stuff.

(Dave Lane) #8

Ok, I’ve installed the wp-discourse WP plugin and have it working. I can log into the Discourse instance by logging into the WP site (directed there via the Discourse login button). Only issue: I want to be able to allow people the option of logging in via WP, but not forcing it… (The OAuth2_basic plugin + WP OAuth Server does, in theory, allow that). The HTTPS issue doesn’t seem to be a problem with the wp-discourse plugin.

(Jay Pfaffman) #9

Aha! Well, that’s why you need Oauth. I just had a client who would liked to have had wordpress authorization for existing members, and turned off SSO because they wanted discourse login for new people.

I think what they really wanted was to import their old users to discourse and have WordPress authenticate against discourse.

(Maarten Busstra) #10

I managed to fix it. In my setup the OAuth plugin for Discourse was supplying an Authorization header, no matter what. WP OAuth Server didn’t like this in combination with the access_token query param. By removing the query param (on the user_json_url), I got it to work!

(Maarten Busstra) #11

(Dave Lane) #12

Crikey, Maarten, your tip fixes things for me - I removed the token-related argument on the end of the “user json url” and it all bursts into life. I will back out my SSL configuration between the Nginx reverse proxy and the Docker container running my Discourse instance and see if it continues to work! Many thanks for posting!

(Maarten Busstra) #13

No problem! I was quite lost myself too. :slight_smile: I found it by intercepting the HTTP request and doing some inspection on it.

(Jay Pfaffman) #14

This looks great, @maartenbusstra. Do I understand correctly that this configuration allows people to optionally log in with Wordpress and could also log in via Google and whatever else?

(Dave Lane) #15

@pfaffman - yes, that’s what I’m seeing. You can offer authentication via an external OAuth2 provider alongside other authentication mechanisms.

(Jay Pfaffman) #16

Excellent. Does this require the Full, not gratis, version of WP OAuth Server?

(Dave Lane) #17

I’m not sure if it could be done with the gratis version. I’ve got a trial of the full version.

(Dave Lane) #18

For the record, I’ve removed the end-to-end HTTPS (including between reverse proxy and the Discourse Docker container) described above, so my SSL terminates at the Docker host’s Nginx reverse proxy (and is http between that and the Docker container, simplifying SSL cert management and various other things), and have found that with the new configuration (with no arguments on any of the URL paths specified in the Discourse Login OAuth2 configuration) works… but it is crucial to retain the proxy_set_header X-Forwarded-Proto https; in your Nginx reverse proxy configuration or you’ll get a URL mis-match error when trying to reload Discourse after authenticating against your WP-OAuth-Server.