Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow verification of JWT with JWKS key. #3

Merged
merged 1 commit into from Sep 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Expand Up @@ -4,17 +4,31 @@ A Discourse Plugin to enable authentication via JSON Web Tokens (JWT).

In the first instance, consider using Discourse's [OpenID Connect](https://meta.discourse.org/t/openid-connect-authentication-plugin/103632) or [OAuth2](https://meta.discourse.org/t/oauth2-basic-support/33879) plugins. These authentication standards are more mature, and include more security features.

### Shared secret vs public keys

JWT tokens can be verified using a shared secret or a public key. When in shared secret mode, the token consumer needs to know the secret used to sign the token by token issuer. Set this token using `jwt_secret` option.
In public key mode, verification step will use a public keys in JWKS format, wich are published by the issuer (eg. under https://example.com/.well-known/jwks.json address). Set this url in `jwt_jwks_url** option.

### Configuration

This plugin provides three site settings for configuration. You must provide all three in your admin panel for the authentication to work correctly:
This plugin provides three site settings for configuration. You must provide either three in your admin panel for the authentication to work correctly:

Shared secret mode:

- `jwt_enabled`
- `jwt_secret`
- `jwt_auth_url`

Public key (JWKS) mode:

- `jwt_enabled`
- `jwt_jwks_url`
- `jwt_auth_url`

Alternatively, you can supply values for these settings via environment variables. Add the following settings to your `app.yml` file in the `env` section:

- `DISCOURSE_JWT_SECRET`
- `DISCOURSE_JWKS_URL`
- `DISCOURSE_JWT_AUTH_URL`

### License
Expand Down
3 changes: 2 additions & 1 deletion config/locales/server.en.yml
Expand Up @@ -2,4 +2,5 @@ en:
site_settings:
jwt_enabled: Enable JWT authentication. Customize user interface text <a href='%{base_path}/admin/customize/site_texts?q=js.login.jwt'>here</a>
jwt_secret: JWT Secret
jwt_auth_url: URL which users will be redirected to for authentication
jwt_auth_url: URL which users will be redirected to for authentication
jwt_jwks_url: URL with JWKS.json (JWT public keys)
3 changes: 2 additions & 1 deletion config/settings.yml
@@ -1,4 +1,5 @@
plugins:
jwt_enabled: false
jwt_secret: ""
jwt_auth_url: ""
jwt_auth_url: ""
jwt_jwks_url: ""
28 changes: 27 additions & 1 deletion plugin.rb
Expand Up @@ -7,6 +7,8 @@
gem "discourse-omniauth-jwt", "0.0.2", require: false

require 'omniauth/jwt'
require 'net/http'
require 'json'

class JWTAuthenticator < Auth::ManagedAuthenticator
def name
Expand All @@ -17,11 +19,12 @@ def register_middleware(omniauth)
omniauth.provider :jwt,
name: 'jwt',
uid_claim: 'id',
required_claims: ['id', 'email', 'name'],
required_claims: ['email', 'first_name', 'last_name'],
setup: lambda { |env|
opts = env['omniauth.strategy'].options
opts[:secret] = SiteSetting.jwt_secret
opts[:auth_url] = SiteSetting.jwt_auth_url
opts[:jwks_loader] = make_jwks_loader
}
end

Expand All @@ -30,6 +33,29 @@ def enabled?
# When this plugin used only global settings, there was no separate enable setting
SiteSetting.jwt_enabled || GlobalSetting.try(:jwt_auth_url)
end

private

def make_jwks_loader
if SiteSetting.jwt_jwks_url
jwks_loader = ->(options) do
@cached_keys = nil if options[:invalidate] # need to reload the keys
if not @cached_keys
begin
jwks = JSON.parse(Net::HTTP.get(URI(SiteSetting.jwt_jwks_url)), symbolize_names: true)
rescue JSON::ParserError => e
Rails.logger.error("Cannot parse JWKS.json from #{SiteSetting.jwt_jwks_url}: #{e}")
jwks = nil
end
@cached_keys ||= jwks
end
@cached_keys
end
else
jwks_loader = nil
end

end
end

auth_provider authenticator: JWTAuthenticator.new