Skip to content

Enable OIDC on OAuth provider to expose user email and profile claims #1051

@jjackson

Description

@jjackson

Problem

The CommCare Connect OAuth provider (/o/introspect/, /o/userinfo/) does not return user profile information (email, name) to OAuth clients. The labs environment (labs.connect.dimagi.com) authenticates users via OAuth against Connect production, but cannot determine the user's email address.

This matters because:

  • Labs needs email to identify @dimagi.com staff users and optimize the UI accordingly
  • Without email, multiple users without emails were stored with email="", causing unique_user_email constraint violations and 500 errors on login (now worked around by storing NULL)
  • The current fallback chain (checking sub field for @ character, checking org data endpoint) is fragile and incomplete

Current State

  • OIDC is not enabledOAUTH2_PROVIDER settings do not include OIDC_ENABLED: True
  • No custom validator — uses default Django OAuth Toolkit validator, which only returns active, scope, client_id, username, token_type, exp, user_id on introspection
  • No openid scope available in the scopes configuration
  • The User model on Connect does have an email field that is populated for users who authenticate via CommCare HQ — the data exists, it's just not exposed via OAuth

Proposed Solution

Follow the same pattern already used in dimagi/connect-id (users/oauth.py), which has a custom ConnectOAuth2Validator that adds claims via get_additional_claims().

On commcare-connect:

  1. Enable OIDC in config/settings/base.py:

    OAUTH2_PROVIDER = {
        ...
        "OIDC_ENABLED": True,
        "OIDC_RSA_PRIVATE_KEY": env("OIDC_RSA_PRIVATE_KEY", default=""),
        "SCOPES": {
            "read": "Read scope",
            "write": "Write scope",
            "export": "Allow exporting data to other platforms using export API's.",
            "openid": "OpenID Connect scope",
        },
    }
  2. Add a custom OAuth validator:

    from oauth2_provider.oauth2_validators import OAuth2Validator
    
    class ConnectOAuth2Validator(OAuth2Validator):
        oidc_claim_scope = OAuth2Validator.oidc_claim_scope
        oidc_claim_scope.update({"email": "openid", "name": "openid"})
    
        def get_additional_claims(self, request):
            return {
                "sub": request.user.username,
                "email": request.user.email or "",
                "name": request.user.name or "",
            }
  3. Set the validator in settings:

    OAUTH2_PROVIDER = {
        ...
        "OAUTH2_VALIDATOR_CLASS": "path.to.ConnectOAuth2Validator",
    }
  4. Generate an RSA private key for OIDC token signing and add to environment/secrets.

On labs (no changes needed from Connect side):

Labs would add openid to LABS_OAUTH_SCOPES and email would arrive natively via introspection/userinfo.

Context

  • Labs OAuth flow: oauth_views.py in commcare_connect/labs/integrations/connect/
  • ConnectID example: users/oauth.py in dimagi/connect-id repo
  • Related labs fix: dimagi/commcare-connect-labs@cf15f97 (store NULL instead of empty string for missing emails)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions