OAuth2 Basic Support

official

(Robin Ward) #1

I’d like to announce the Discourse OAuth2 Basic plugin. The goal of the plugin is to support basic OAuth2 providers assuming they have a JSON API endpoint where user details can be retrieved by token. The project README has a lot more information so I’ll re-post it here:


discourse-oauth2-basic

This plugin allows you to use a basic OAuth2 provider as authentication for Discourse. It should work with many providers, with the caveat that they must provide a JSON endpoint for retrieving information about the user you are logging in.

This is mainly useful for people who are using login providers that aren’t very popular. If you want to use Google, Facebook or Twitter, those are included out of the box and you don’t need this plugin. You can also look for other login providers in our Github Repo.

Usage

Part 1: Basic Configuration

First, set up your Discourse application remotely on your OAuth2 provider. It will require a Redirect URI which should be:

http://DISCOURSE_HOST/auth/oauth2_basic/callback

Replace DISCOURSE_HOST with the approriate value, and make sure you are using https if enabled. The OAuth2 provider should supply you with a client ID and secret, as well as a couple of URLs.

Visit your Admin > Settings > Login and fill in the basic configuration for the OAuth2 provider:

  • oauth2_enabled - check this off to enable the feature

  • oauth2_client_id - the client ID from your provider

  • oauth2_client_secret - the client secret from your provider

  • oauth2_authorize_url - your provider’s authorization URL

  • oauth2_token_url - your provider’s token URL.

If you can’t figure out the values for the above settings, check the developer documentation from your provider or contact their customer support.

Part 2: Configuring the JSON User Endpoint

Discourse is now capable of receiving an authorization token from your OAuth2 provider. Unfortunately, Discourse requires more information to be able to complete the authentication.

We require an API endpoint that can be contacted to retrieve information about the user based on the token.

For example, the OAuth2 provider SoundCloud provides such a URL. If you have an OAuth2 token for SoundCloud, you can make a GET request to https://api.soundcloud.com/me?oauth_token=A_VALID_TOKEN and will get back a JSON object containing information on the user.

To configure this on Discourse, we need to set the value of the oauth2_user_json_url setting. In this case, we’ll input the value of:

https://api.soundcloud.com/me?oauth_token=:token

The part with :token tells Discourse that it needs to replace that value
with the authorization token it received when the authentication completed.

There is one last step to complete. We need to tell Discourse what
attributes are available in the JSON it received. Here’s a sample
response from SoundCloud:

{
  "id": 3207,
  "permalink": "jwagener",
  "username": "Johannes Wagener",
  "uri": "https://api.soundcloud.com/users/3207",
  "permalink_url": "http://soundcloud.com/jwagener",
  "avatar_url": "http://i1.sndcdn.com/avatars-000001552142-pbw8yd-large.jpg?142a848",
  "country": "Germany",
  "full_name": "Johannes Wagener",
  "city": "Berlin"
}

The oauth2_json_user_id_path, oauth2_json_username_path, oauth2_json_name_path and oauth2_json_email_path variables should be set to point to the appropriate attributes in the JSON.

The only mandatory attribute is id - we need that so when the user logs on in the future that we can pull up the correct account. The others are great if available – they will make the signup process faster for the user as they will be pre-populated in the form.

Here’s how I configured the JSON path settings:

  oauth2_json_user_id_path: 'id'
  oauth2_json_username_path: 'permalink'
  oauth2_json_name_path: 'full_name'

I used permalink because it seems more similar to what Discourse expects for a username than the username in their JSON. Notice I omitted the email path: SoundCloud do not provide an email so the user will have to provide and verify this when they sign up the first time on Discourse.

If the properties you want from your JSON object are nested, you can use periods. So for example if the API returned a different structure like this:

{
  "user": {
    "id": 1234,
    "email": {
      "address": "test@example.com"
    }
  }
}

You could use user.id for the oauth2_json_user_id_path and user.email.address for oauth2_json_email_path.

:warning: Warning - if you set oauth2_json_email_path, the OAuth2 provider must be validating the email address is one the user owns. Failure to do this can result in account takeover on Discourse.

Good luck setting up custom OAuth2 on your Discourse!


Keycloak with Discourse
Setting up Salesforce auth using OAuth2 basic support plugin
Login from another user database
SSO Drupal site while keeping Facebook Login and Google+ Login
Shopify Integration
How to use Auth0 with the OAuth2 Basic Plugin
Discord Oauth2 Plugin
How can we enable Auth0 SSO in Discourse
OAuth2 integration with Drupal
SSO for some but not for everybody?
About the oauth2-basic category
OAuth2 Custom Redirects Plugin
Migrating from Jive Clearspace to Discourse
Auth0: Single Sign On for Enterprise and support for 20+ Social Providers
Oauth config for memberful (not working)
(Erlend Sogge Heggen) #2

123 posts were split to a new topic: OAuth2 Basic - Legacy replies


(Erlend Sogge Heggen) #10

3 posts were split to a new topic: Support for OpenID Connect (OIDC)


(Erlend Sogge Heggen) #112

2 posts were split to a new topic: Support for full_screen_login feature like saml plugin


(Erlend Sogge Heggen) #115

5 posts were split to a new topic: Configuring oauth2 with doxology


(Erlend Sogge Heggen) #136

2 posts were split to a new topic: Support for WP OAuth Server


(Erlend Sogge Heggen) #138

4 posts were split to a new topic: Locked out after update


(Erlend Sogge Heggen) #142

2 posts were split to a new topic: Making SSO via OAuth2 Basic plugin work with our Identity Provider


(Erlend Sogge Heggen) #144

A post was split to a new topic: Support for specifying a custom scope


(Erlend Sogge Heggen) #145

2 posts were split to a new topic: 500 error upon sending callback request


(Erlend Sogge Heggen) #146

Please post new replies to the #plugin:oauth2-basic category.


(Tegebe) #147

Hi there,

I initially used the OAuth2-Plugin with the Bitnami provided Discourse docker image(s) and it worked as expected.
Now I switched over to the Discourse cloud installer that creates its own all-in-one docker container.
I migrated the old PostgresDB.

When I now try to register or authorize an user using OAuth2 against a company based OAuth-Endpoint (NetIQ Access Manager) I get the following error in “production.log”:

Started GET "/" for 127.0.0.1 at 2018-10-01 13:33:01 +0000
Started GET "/auth/oauth2_basic" for 127.0.0.1 at 2018-10-01 13:33:06 +0000
(oauth2_basic) Setup endpoint detected, running now.
(oauth2_basic) Request phase initiated.
Started GET "/auth/oauth2_basic/callback?code=<lots-of-stuff-here>&state=35099b84bece8314370852261e8ee1301c32df3f37b84586&scope=my-profile" for 127.0.0.1 at 2018-10-01 13:33:07 +0000
(oauth2_basic) Setup endpoint detected, running now.
(oauth2_basic) Callback phase initiated.
Faraday::SSLError (SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate))
/usr/local/lib/ruby/2.5.0/net/protocol.rb:44:in `connect_nonblock'

I also imported the the server cert in the container using ‘update-ca-certs’
Has anyone an idea, what the problem could be?

Thank you in advance,
Thilo


(Rafael dos Santos Silva) #148

It’s still the certificate.

Try doing a curl from inside the container with verbose logs to easily reproduce the problem.


(Jay Pfaffman) #149

It looks like a certificate problem. Do both ends have valid https certificates?


(Tegebe) #150

Thank you for the curl suggestion. I noticed that the curl request to the OAuth provider was done using our corporate proxy (and failed). After setting the “no_proxy” env variable in app.yml to exclude the OAuth provider. The curl request came back with a valid response regarding the cert.

root@discourse:/# curl -v https://login.xyz.de
* Rebuilt URL to: https://login.xyz.de/
*   Trying 192.168.230.198...
* Connected to login.xyz.de (192.168.230.198) port 443 (#0)
* found 148 certificates in /etc/ssl/certs/ca-certificates.crt
* found 594 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / ECDHE_RSA_AES_128_GCM_SHA256
* 	 server certificate verification OK
* 	 server certificate status verification SKIPPED
* 	 common name: *.xyz.de (matched)
* 	 server certificate expiration date OK
* 	 server certificate activation date OK
* 	 certificate public key: RSA
* 	 certificate version: #3
* 	 subject: C=DE,postalCode=12345,ST=Berlin,L=Berlin,street=The street,O=XYZ.,OU=PremiumSSL Wildcard,CN=*.xyz.de
* 	 start date: Thu, 31 May 2018 00:00:00 GMT
* 	 expire date: Sat, 18 Jul 2020 23:59:59 GMT
* 	 issuer: C=GB,ST=Greater Manchester,L=Salford,O=COMODO CA Limited,CN=COMODO RSA Organization Validation Secure Server CA
* 	 compression: NULL
* ALPN, server did not agree to a protocol
> GET / HTTP/1.1
> Host: login.xyz.de
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 302 Found
< Server: Apache-Coyote/1.1
< Strict-Transport-Security: max-age=31536000
< X-Frame-Options: SAMEORIGIN
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Set-Cookie: JSESSIONID=abcdeffhifklmnopq; Path=/; Secure; HttpOnly
< Location: /nidp
< Content-Type: text/html;charset=ISO-8859-1
< Content-Length: 0
< Date: Tue, 02 Oct 2018 15:48:23 GMT
<
* Connection #0 to host login.xyz.de left intact

But know luck with Discourse - the error in production.log is still the same.


(Rafael dos Santos Silva) #151

Ruby has very bad support for *_proxy environment variables, at least that was the status quo 4 years ago when I had to bootstrap Discourse in a server that had to use those to get to the internet.

What may help you debug is to enter the app (./launcher enter app; rails c) and then issue requests from Ruby to check for errors.


(Tegebe) #152

When executing Net::HTTP:Get URI('https://login.xyz.de') in rails console I get no error or exception.
I have tried to set SSL_CERT_FILE=/shared/cacert.pem as environment variable using the curl ca certs bundle as pointed out in several ruby related posts. Still no difference.

Any help would be appreciated…

Regards, Thilo


(Tegebe) #153

I have found this useful project:

This one seems to be ok (as I would expect from the OAuth-Plugin):

root@discourse:/ssl-tools# ruby doctor.rb login.xyz.de:443
/usr/local/bin/ruby (2.5.1-p57)
OpenSSL 1.0.2g  1 Mar 2016: /usr/lib/ssl
SSL_CERT_DIR=""
SSL_CERT_FILE="/shared/cacert.pem"

HEAD https://login.xyz.de:443
OK

This host uses a self signed cert issued by a CA that is unknown to the Discourse container:

root@discourse:/ssl-tools# ruby doctor.rb <other-internal-host-with-self-signed-cert>:443
/usr/local/bin/ruby (2.5.1-p57)
OpenSSL 1.0.2g  1 Mar 2016: /usr/lib/ssl
SSL_CERT_DIR=""
SSL_CERT_FILE="/shared/cacert.pem"

HEAD <other-internal-host-with-self-signed-cert>:443
OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed (self signed certificate in certificate chain)

The server presented a certificate that could not be verified:
  subject: /O=XYZ/OU=Organizational CA
  issuer: /O=XYZ/OU=Organizational CA
  error code 19: self signed certificate in certificate chain

(Jay Pfaffman) #154

Is there a reason not to use a valid and well known certificate from let’s encrypt?


(Tegebe) #155

Yes, it a corporate environment using a wildcard cert and I am only one of several clients.