-
Notifications
You must be signed in to change notification settings - Fork 819
OpenID Connect support #915
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
Merged
Merged
Changes from 29 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
a88d808
Openid Connect Core support - Round 3
thinkwelltwd b05e0c2
Make IDToken admin class swappable
tevansuk 183b02a
Make OIDC support optional
tevansuk 27af17e
Add tests for OIDC userinfo view
tevansuk ffe2b05
Add test for creating the OIDC issuer url for JWTs
tevansuk 8787ee3
Add ID token generation and validation tests
tevansuk e7128bd
Add tests for OAuth2ProviderSettings
tevansuk 2400b7d
Add tests for IDToken model methods
tevansuk 3b4132d
Remove unnecessary __future__ declarations
tevansuk 7aadea8
Enable OIDC only views only when using OIDC
tevansuk fec1774
Remove mistakenly committed comment
tevansuk 7c4ee37
Add OIDC documentation
tevansuk 9d07a1e
Add OIDC support change to CHANGELOG.md
tevansuk 2bc0c23
Support nonce and claims in OIDC auth
tevansuk 760dda1
Fix py3.5 tests getting scopes in unexpected order
tevansuk bae5e75
Simplify and optimise get_authorization_code_scopes
tevansuk 6ad4798
Serve JWKs using well-known URL
tevansuk cee11b0
Allow POST for OIDC UserInfo endpoint
tevansuk a8b5879
Refactor how OIDC issuer url is generated
tevansuk ee58567
Support HS256 for OIDC
tevansuk d8ddfa2
fix: json.loads on python 3.5 requires a str
tevansuk 988dac3
Remove changes to create_authorization_response
tevansuk a13e6fd
Reduce changes compared to master
tevansuk af8ad08
Use simpler import name for oauthlib OIDC server
tevansuk 38a7f6c
Fix another reference to oauthlib.openid.Server
tevansuk 57b8051
Merge remote-tracking branch 'upstream/master' into feat/oidc-round-t…
tevansuk ad2dde6
Add indirect six dependency from jwcrpyto
tevansuk c288e7a
Django master now supports only python 3.8+
tevansuk d6f6082
Store jti instead of the ID token contents
tevansuk e1c443f
Merge remote-tracking branch 'origin/master' into feat/oidc-round-three
tevansuk 68fb7b1
Code review changes
tevansuk dd2f833
Generate the correct issuer URL from oauthlib
tevansuk 1f92aeb
Merge remote-tracking branch 'upstream/master' into feat/oidc-round-t…
tevansuk e9183af
Update OIDC docs
tevansuk b39c2a8
Merge remote-tracking branch 'upstream/master' into feat/oidc-round-t…
tevansuk de73462
Merge branch 'master' into feat/oidc-round-three
n2ygk 36ef015
Krl/oidc round three fixes (#1)
kristianrunelarsen 2dfba41
Fix linting from PR
tevansuk d7ab9f1
Handle invalid tokens in userinfo endpoint
tevansuk ec06198
Update a comment
tevansuk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ pip-log.txt | |
|
|
||
| # Unit test / coverage reports | ||
| .cache | ||
| .pytest_cache | ||
| .coverage | ||
| .tox | ||
| .pytest_cache/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,7 @@ Index | |
| views/details | ||
| models | ||
| advanced_topics | ||
| oidc | ||
| signals | ||
| settings | ||
| resource_server | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,306 @@ | ||
| OpenID Connect | ||
| ++++++++++++++ | ||
|
|
||
| OpenID Connect support | ||
| ====================== | ||
|
|
||
| ``django-oauth-toolkit`` supports OpenID Connect (OIDC), which standardizes | ||
| authentication flows and provides a plug and play integration with other | ||
| systems. OIDC is built on top of OAuth 2.0 to provide: | ||
|
|
||
| * Generating ID tokens as part of the login process. These are JWT that | ||
| describe the user, and can be used to authenticate them to your application. | ||
| * Metadata based auto-configuration for providers | ||
| * A user info endpoint, which applications can query to get more information | ||
| about a user. | ||
|
|
||
| Enabling OIDC doesn't affect your existing OAuth 2.0 flows, these will | ||
| continue to work alongside OIDC. | ||
|
|
||
| We support: | ||
|
|
||
| * OpenID Connect Authorization Code Flow | ||
| * OpenID Connect Implicit Flow | ||
| * OpenID Connect Hybrid Flow | ||
|
|
||
|
|
||
| Configuration | ||
| ============= | ||
|
|
||
| OIDC is not enabled by default because it requires additional configuration | ||
| that must be provided. ``django-oauth-toolkit`` supports two different | ||
| algorithms for signing JWT tokens, ``RS256``, which uses asymmetric RSA keys (a | ||
| public key and a private key), and ``HS256``, which uses a symmetric key. | ||
|
|
||
| It is preferrable to use ``RS256``, because this produces a token that can be | ||
| verified by anyone using the public key (which is made available and | ||
| discoverable by OIDC service auto-discovery, included with | ||
| ``django-oauth-toolkit``). ``HS256`` on the other hand uses the | ||
| ``client_secret`` in order to verify keys. This is simpler to implement, but | ||
| makes it harder to safely verify tokens. | ||
|
|
||
| Using ``HS256`` also means that you cannot use the Implicit or Hybrid flows, | ||
| or verify the tokens in public clients, because you cannot disclose the | ||
| ``client_secret`` to a public client. If you are using a public client, you | ||
| must use ``RS256``. | ||
|
|
||
|
|
||
| Creating RSA private key | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| To use ``RS256`` requires an RSA private key, which is used for signing JWT. You | ||
| can generate this using the ``openssl`` tool:: | ||
|
|
||
| openssl genrsa -out oidc.key 4096 | ||
|
|
||
| This will generate a 4096-bit RSA key, which will be sufficient for our needs. | ||
|
|
||
| .. warning:: | ||
| The contents of this key *must* be kept a secret. Don't put it in your | ||
| settings and commit it to version control! | ||
|
|
||
| If the key is ever accidentally disclosed, an attacker could use it to | ||
| forge JWT tokens that verify as issued by your OAuth provider, which is | ||
| very bad! | ||
|
|
||
| If it is ever disclosed, you should immediately replace the key. | ||
|
|
||
| Safe ways to handle it would be: | ||
|
|
||
| * Store it in a secure system like `Hashicorp Vault`_, and inject it in to | ||
| your environment when running your server. | ||
| * Store it in a secure file on your server, and use your initialization | ||
| scripts to inject it in to your environment. | ||
|
|
||
| .. _Hashicorp Vault: https://www.hashicorp.com/products/vault | ||
|
|
||
| Now we need to add this key to our settings and allow the ``openid`` scope to | ||
| be used. Assuming we have set an environment variable called | ||
| ``OIDC_RSA_PRIVATE_KEY``, we can make changes to our ``settings.py``:: | ||
|
|
||
| import os.environ | ||
|
|
||
| OAUTH2_PROVIDER = { | ||
| "OIDC_ENABLED": True, | ||
| "OIDC_RSA_PRIVATE_KEY": os.environ.get("OIDC_RSA_PRIVATE_KEY"), | ||
| "SCOPES": { | ||
| "openid": "OpenID Connect scope", | ||
| # ... any other scopes that you use | ||
| }, | ||
| # ... any other settings you want | ||
| } | ||
|
|
||
| If you are adding OIDC support to an existing OAuth 2.0 provider site, and you | ||
| are currently using a custom class for ``OAUTH2_SERVER_CLASS``, you must | ||
| change this class to derive from ``oauthlib.openid.Server`` instead of | ||
| ``oauthlib.oauth2.Server``. | ||
|
|
||
| With ``RSA`` key-pairs, the public key can be generated from the private key, | ||
| so there is no need to add a setting for the public key. | ||
|
|
||
| Using ``HS256`` keys | ||
| ~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| If you would prefer to use just ``HS256`` keys, you don't need to create any | ||
| additional keys, ``django-oauth-toolkit`` will just use the application's | ||
| ``client_secret`` to sign the JWT token. | ||
|
|
||
| In this case, you just need to enable OIDC and add ``openid`` to your list of | ||
| scopes in your ``settings.py``:: | ||
|
|
||
| OAUTH2_PROVIDER = { | ||
| "OIDC_ENABLED": True, | ||
| "SCOPES": { | ||
| "openid": "OpenID Connect scope", | ||
| # ... any other scopes that you use | ||
| }, | ||
| # ... any other settings you want | ||
| } | ||
|
|
||
| .. info:: | ||
| If you want to enable ``RS256`` at a later date, you can do so - just add | ||
| the private key as described above. | ||
|
|
||
| Setting up OIDC enabled clients | ||
| =============================== | ||
|
|
||
| Setting up an OIDC client in ``django-oauth-toolkit`` is simple - in fact, all | ||
| existing OAuth 2.0 Authorization Code Flow and Implicit Flow applications that | ||
| are already configured can be easily updated to use OIDC by setting the | ||
| appropriate algorithm for them to use. | ||
|
|
||
| You can also switch existing apps to use OIDC Hybrid Flow by changing their | ||
| Authorization Grant Type and selecting a signing algorithm to use. | ||
|
|
||
| You can read about the pros and cons of the different flows in `this excellent | ||
| article`_ from Robert Broeckelmann. | ||
|
|
||
| .. _this excellent article: https://medium.com/@robert.broeckelmann/when-to-use-which-oauth2-grants-and-oidc-flows-ec6a5c00d864 | ||
|
|
||
| OIDC Authorization Code Flow | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| To create an OIDC Authorization Code Flow client, create an ``Application`` | ||
| with the grant type ``Authorization code`` and select your desired signing | ||
| algorithm. | ||
|
|
||
| When making an authorization request, be sure to include ``openid`` as a | ||
| scope. When the code is exchanged for the access token, the response will | ||
| also contain an ID token JWT. | ||
|
|
||
| If the ``openid`` scope is not requested, authorization requests will be | ||
| treated as standard OAuth 2.0 Authorization Code Grant requests. | ||
|
|
||
| With ``PKCE`` enabled, even public clients can use this flow, and it is the most | ||
| secure and recommended flow. | ||
|
|
||
| OIDC Implicit Flow | ||
| ~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| OIDC Implicit Flow is very similar to OAuth 2.0 Implicit Grant, except that | ||
| the client can request a ``response_type`` of ``id_token`` or ``id_token | ||
| token``. Requesting just ``token`` is also possible, but it would make it not | ||
| an OIDC flow and would fall back to being the same as OAuth 2.0 Implicit | ||
| Grant. | ||
|
|
||
| To setup an OIDC Implicit Flow client, simply create an ``Application`` with | ||
| the a grant type of ``Implicit`` and select your desired signing algorithm, | ||
| and configure the client to request the ``openid`` scope and an OIDC | ||
| ``response_type`` (``id_token`` or ``id_token token``). | ||
|
|
||
|
|
||
| OIDC Hybrid Flow | ||
| ~~~~~~~~~~~~~~~~ | ||
|
|
||
| OIDC Hybrid Flow is a mixture of the previous two flows. It allows the ID | ||
| token and an access token to be returned to the frontend, whilst also | ||
| allowing the backend to retrieve the ID token and an access token (not | ||
| necessarily the same access token) on the backend. | ||
|
|
||
| To setup an OIDC Hybrid Flow application, create an ``Application`` with a | ||
| grant type of ``OpenID connect hybrid`` and select your desired signing | ||
| algorithm. | ||
|
|
||
|
|
||
| Customizing the OIDC responses | ||
| ============================== | ||
|
|
||
| This basic configuration will give you a basic working OIDC setup, but your | ||
| ID tokens will have very few claims in them, and the ``UserInfo`` service will | ||
| just return the same claims as the ID token. | ||
|
|
||
| To configure all of these things we need to customize the | ||
| ``OAUTH2_VALIDATOR_CLASS`` in ``django-oauth-toolkit``. Create a new file in | ||
| our project, eg ``my_project/oauth_validator.py``:: | ||
|
|
||
| from oauth2_provider.oauth2_validators import OAuth2Validator | ||
|
|
||
|
|
||
| class CustomOAuth2Validator(OAuth2Validator): | ||
| pass | ||
|
|
||
|
|
||
| and then configure our site to use this in our ``settings.py``:: | ||
|
|
||
| OAUTH2_PROVIDER = { | ||
| "OAUTH2_VALIDATOR_CLASS": "my_project.oauth_validators.CustomOAuth2Validator", | ||
| # ... other settings | ||
| } | ||
|
|
||
| Now we can customize the tokens and the responses that are produced by adding | ||
| methods to our custom validator. | ||
|
|
||
|
|
||
| Adding claims to the ID token | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| By default the ID token will just have a ``sub`` claim (in addition to the | ||
| required claims, eg ``iss``, ``aud``, ``exp``, ``iat``, ``auth_time`` etc), | ||
| and the ``sub`` claim will use the primary key of the user as the value. | ||
| You'll probably want to customize this and add additional claims or change | ||
| what is sent for the ``sub`` claim. To do so, you will need to add a method to | ||
| our custom validator:: | ||
|
|
||
| class CustomOAuth2Validator(OAuth2Validator): | ||
|
|
||
| def get_additional_claims(self, request): | ||
| return { | ||
| "sub": request.user.email, | ||
| "first_name": request.user.first_name, | ||
| "last_name": request.user.last_name, | ||
| } | ||
|
|
||
| .. note:: | ||
| This ``request`` object is not a ``django.http.Request`` object, but an | ||
| ``oauthlib.common.Request`` object. This has a number of attributes that | ||
| you can use to decide what claims to put in to the ID token: | ||
|
|
||
| * ``request.scopes`` - a list of the scopes requested by the client when | ||
| making an authorization request. | ||
| * ``request.claims`` - a dictionary of the requested claims, using the | ||
| `OIDC claims requesting system`_. These must be requested by the client | ||
| when making an authorization request. | ||
| * ``request.user`` - the django user object. | ||
|
|
||
| .. _OIDC claims requesting system: https://openid.net/specs/openid-connect-core-1_0.html#ClaimsParameter | ||
|
|
||
| What claims you decide to put in to the token is up to you to determine based | ||
| upon what the scopes and / or claims means to your provider. | ||
|
|
||
|
|
||
| Adding information to the ``UserInfo`` service | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| The ``UserInfo`` service is supplied as part of the OIDC service, and is used | ||
| to retrieve more information about the user than was supplied in the ID token | ||
| when the user logged in to the OIDC client application. It is optional to use | ||
| the service. The service is accessed by making a request to the | ||
| ``UserInfo`` endpoint, eg ``/o/userinfo/`` and supplying the access token | ||
| retrieved at login as a ``Bearer`` token. | ||
|
|
||
| Again, to modify the content delivered, we need to add a function to our | ||
| custom validator. The default implementation adds the claims from the ID | ||
| token, so you will probably want to re-use that:: | ||
|
|
||
| class CustomOAuth2Validator(OAuth2Validator): | ||
|
|
||
| def get_userinfo_claims(self, request): | ||
| claims = super().get_userinfo_claims() | ||
| claims["color_scheme"] = get_color_scheme(request.user) | ||
| return claims | ||
|
|
||
|
|
||
| OIDC Views | ||
| ========== | ||
|
|
||
| Enabling OIDC support adds three views to ``django-oauth-toolkit``. When OIDC | ||
| is not enabled, these views will log that OIDC support is not enabled, and | ||
| return a ``404`` response, or if ``DEBUG`` is enabled, raise an | ||
| ``ImproperlyConfigured`` exception. | ||
|
|
||
| In the docs below, it assumes that you have mounted the | ||
| ``django-oauth-toolkit`` at ``/o/``. If you have mounted it elsewhere, adjust | ||
| the URLs accordingly. | ||
|
|
||
|
|
||
| ConnectDiscoveryInfoView | ||
| ~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| Available at ``/o/.well-known/openid-configuration/``, this view provides auto | ||
| discovery information to OIDC clients, telling them the JWT issuer to use, the | ||
| location of the JWKs to verify JWTs with, the token and userinfo endpoints to | ||
| query, and other details. | ||
|
|
||
|
|
||
| JwksInfoView | ||
| ~~~~~~~~~~~~ | ||
|
|
||
| Available at ``/o/.well-known/jwks.json``, this view provides details of the key used to sign | ||
| the JWTs generated for ID tokens, so that clients are able to verify them. | ||
|
|
||
|
|
||
| UserInfoView | ||
| ~~~~~~~~~~~~ | ||
|
|
||
| Available at ``/o/userinfo/``, this view provides extra user details. You can | ||
| customize the details included in the response as described above. | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.