Category: OAuth

Best Django Openidc Package

I think we know the security benefits and the development benefits of using a delegated authentication protocol like OpenIDC or SAML.
However, actually doing the integration in the application can be difficult at times.

There is a lack of documentation and guidance on the best modules or packages to use for the various frameworks.
In this post I will be looking at the options available for Django when authenticating with an OpenIDC provider - Keycloak.

Using Django as an OpenIDC provider is not what I want - although it is possible with the django openidc provider package

First port of call is an anonymous web search and checking django packages authentication. Django packages seems to put everything in the auth basket where we specifically want to look at OpenIDC clients.

What we are Looking For

  • Supports or is compatible with Django 3
  • Explicit OpenIDC support (Oauth 2 is not good enough we also need the identity)
  • Good Documentation
  • A client that is not closely bound to a specific provider - but that is closely bound to the protocol specification.
  • Integrated with django admin - ie. logging in from django admin redirects
  • A client that can use django's existing permissions and a nice way to integrate with the provider maybe by way of groups...

OpenIDC Django Packages we are reviewing

If there is no decent documentation the package is discarded from review

There are ways to integrate apache and nginx with OpenIDC - the problem is making use of django's permission. Will that still be possible.

Method of Testing

We going to create a blank django project, add a model (Beer) and 2 django groups. The first group "customers" can view the beers - as staff on the admin site. The second group "creators" can add, view and change beers on the admin site.

Django Admin SSO

Decent - although documentation is lacking and it defaults to google SSO. You have to check the example settings and change it to keycloak.
Initially it did not get the id token as scope was set to email.
I had to edit the code and change the scope to opendic.
Then it would only progress is email_verified was True...so I had to edit that code as well.

Weirdly in the changelog it mentions:

Using OpenID is now deprecated and OpenID support will be removed in a future release

OpenIDC is the future.

This package contains migrations that create the assignments table.

Problems

The problem with this library is that it forces assignments to be created before users are allowed to login. An assignment maps an OpenIDC identity to a local user - that must already exist.

This increases the administrative burden and does not do what openIDC intends - delegating auth (and groups) to the identity provider.

Conclusion

A decent lightweight choice - purely for admin login. Just a bit of admin on setting up a user and an assignment.

Django Social Registration

Requires 'django.contrib.sites'.

Conclusion

Excluded. Too old and not compatible with Django 3.

Python Social Auth

The repo looks very old in terms of recent updates on github.
However the social-core is where most of the updates happen.

Social auth core is a dependecy of the project:

social-auth-app-django==4.0.0
social-auth-core==4.1.0

The docs are not updated for keycloak settings but they are documented in the code:

AUTHENTICATION_BACKENDS = (
    'social_core.backends.keycloak.KeycloakOAuth2',
    'django.contrib.auth.backends.ModelBackend',
)

SOCIAL_AUTH_ADMIN_USER_SEARCH_FIELDS = ['username', 'first_name', 'email']

# OpenIDC URL
SOCIAL_AUTH_KEYCLOAK_KEY = 'test-django-oidc'
SOCIAL_AUTH_KEYCLOAK_SECRET = 'a7a41-245e-...'
SOCIAL_AUTH_KEYCLOAK_PUBLIC_KEY = \
    'MIIBIjANBxxxdSD'
SOCIAL_AUTH_KEYCLOAK_AUTHORIZATION_URL = \
    'https://iam.example.com/auth/realms/voxcloud-staff/protocol/openid-connect/auth'
SOCIAL_AUTH_KEYCLOAK_ACCESS_TOKEN_URL = \
    'https://iam.example.com/auth/realms/voxcloud-staff/protocol/openid-connect/token'

SOCIAL_AUTH_KEYCLOAK_ID_KEY = 'email'

Problems

I was getting an issues where the client_id (audience) set up on django was not present in the aud key of the id_token JWT. Creating the error: Invalid audience.

The workaround to solve the client_id not in the audiences is on stackoverflow.

The gist of it is in you client's mappers tab - create an audience mapper for your client_id.

You also have to override the django tempalte for admin login to add the link for keycloak login.

Conclusion

Overall a good quality package. Works well. Now it is just about ensuring the user gets staff status pulled through with the correct permissions from keycloak mapped to django groups.

To give a user the is_staff and relevant group assignments - the flow with python social auth is to create a pipeline as mentioned in this github issue. Here are the docs for extending the pipeline.

So we can modify the pipelines to match our requirements. If we want users to be automatically created or not etc. The default pipeline can be overriden by this setting:

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
    'my_module.pipeline.my_custom_pipeline'
)

Pipelines can also be defined per backend...for example: SOCIAL_AUTH_TWITTER_PIPELINE

We just need to be clear on the use of groups and roles in keycloak. Groups define the types of users in an organisation. Composite roles are for managing the application side.

In other words, groups are only an entity on keycloak side - they are not ever sent to client applications. Only the roles they assign are.

Overall I think this is a great choice

Django Boss SSO 2

Leverages other libraries namely drf-oidc-auth and mozilla-django-oidc to provide openidc auth with keycloak for django and django rest framework.

The readme has inconsistencies and does not lsit all required packages. There is too many libraries that are too similar used in my opinion.

django-oidc is used but not listed and is not django > 2 compliant.

Yes, it is not at the level required.

I do not recommend this package

Django Keycloak Auth

Specifically for drf - django rest framework.

Each viewset has to be explicity given roles:

keycloak_roles = {
    'GET': ['judge'],
}

I don't like this - I would prefer leveraging of djangos groups and permissions.

I am going to skip reviewing this package.

Django AllAuth

I have an issue where the response was the encoded JWT but the keycloak backend was expecting a json response.

So raised an issue and will look at this again when there is a change...

Turns out this was a problem on the settings on the provider side (keycloak) - the Userinfo signed response algorithm must be unisgned. I had it as RS256.

signed-userinfo-endpoint-keycloak

Mozzila Django OpenIDC

Very straight forward in setup.
We need to do a bit of nigly work to get the login link on the admin page shown below.

Remember the link the initialise the redirect code flow is:

{% translate 'Login with SSO Provider' %}

Then similar to the pipelines in python-social-auth you need to define a function for extra stuff like linking groups up and making people superusers - first names and last names etc. Although not as eligant as social auth does it...

You end up having to inherit from OIDCAuthenticationBackend and heavily customise the backend to your liking.
More so that necessary with python-social-auth.

A decent library choice buy architecturally python-social-auth is better.

Tutorial on how to add a Login with OpenIDC link to the Admin Page

  1. Look for templates in your project folder templates directory by adding to settings.py in the TEMPLATES part:

    [os.path.join(BASE_DIR, ' ,'templates'), ]

    Remember to import os

  2. Now add the template for the login page for that folder - copy the content from env/lib/../django/contrib/admin/templates/admin/login.html into <your_project>/templates/admin/custom_login.html

    Make the changes to the template (a link to OpenIDC login)

  3. Now tell the admin site to use that template - in urls.py

    admin.site.login_template = 'admin/custom_login.html'

Keycloak OIDC

An enhancement on top of mozilla-django-openidc - adding the ability to link keycloak roles to groups on django.

Still not 100% now yet - as teh is_staff and is_superuser will have to be handled in a custom manner.

Using django-oauth-toolkit for Client credentials Oauth Flow

I've been wanting to secure my api - so unidentified and unathorized parties cannot view, update, create or delete data.
This api is internal to the company and will only be used by other services - in other words no end users.
Hence the delegation of authorization need not happen and the services will be authneticating directly with the api.

That is why the Oauth client credentials flow is used - it is for server to server communication. (As far as I know)

There is alot of conflicting information on Oauth but in the RFC6749 on Oauth 2 Client credentials is mentioned:

1.3.4.  Client Credentials

   The client credentials (or other forms of client authentication) can
   be used as an authorization grant when the authorization scope is
   limited to the protected resources under the control of the client,
   or to protected resources previously arranged with the authorization
   server.  Client credentials are used as an authorization grant
   typically when the client is acting on its own behalf (the client is
   also the resource owner) or is requesting access to protected
   resources based on an authorization previously arranged with the
   authorization server.

Nordic API's: Securing the API Stronghold book mentions:

Oauth: It’s for delegation, and delegation only

I agree except when the client is the resource owner in the client credentials instance.
In that case surely there is no delegation?

Should we use it

What is the advantage over a basic auth or token authentication method?

It seems to just be an added step for the client but the key is that the token expires. So if a bad actor gets our token it will not last long before it is of no use.
The client id and secret is the thing that is used to generate tokend for future calling of the api.

Difference between Resource Owner Password Based flow and client Credentials

Django-oauth-tollkit provides both and their example uses the resource owner password based flow.
In both cases the resource owner is the client - so there is no delegation.

So what is the difference?

I checked on stackoverflow, and it turns out I was wrong.

In the resource owner client based way, the resource owner (end user) trusts the client application enough to give it it's username and password.
We don't really want this.

Implementing Client Credentials flow

Since users are not going to use the API and only services/clients will, I want to disable the other authorization flows and disable registering of clients.

I will manage the clients and they will be the resource owners.

So if you follow the information in the django-oauth-toolkit and setting it up for client credentials that should help

Permissions are significantly different from Django Permissions

What I found out durinng testing is that OauthToolkit implements it's own seperate permissions. So if you were wanting to use django model permissions (add, change, view and delete), you don't be able to.

Wait...I spoke too fast.

You can allow this with:

permission_classes = [IsAuthenticatedOrTokenHasScope, DjangoModelPermission]

However that means that you actually have to test with scopes if you expect a client to use it with Oauth and not django auth.

This is the view to use ClientProtectedResourceView