OpenID Connect Client with Python

OpenID Connect Client with Python

Code Examples / website-integration

Overview

This tutorial provides a basic demo application created using Python 3, which shows how to implement an OpenID Connect flow to obtain access and ID tokens from an OpenID Provider.

Configure the Curity Identity Server

In the Admin UI, create an OAuth Client with the following properties. Also ensure that at least one working authenticator is used, so that you are able to perform a user login once the sample is running.

  • Client ID: client-python
  • Capabilites: Code Flow
  • Authentication Method: Secret
  • Client Secret: Password1
  • Redirect URI: https://localhost:5443/callback
  • Post Logout Redirect URI: https://localhost:5443
  • scope: openid profile email address phone

Configure the Code Sample

Next download the code sample and view its configuration in the settings.json file:

{
  "issuer": "https://localhost:8443/oauth/v2/oauth-anonymous",
  "api_endpoint": "https://localhost:3000",
  "verify_ssl_server": false,
  "debug": true,
  "scope": "openid profile email address phone",
  "send_parameters_via": "query",
  "client_id": "python-client",
  "client_secret": "Password1"
}

The sample uses the following base URLs by default, and these can be updated to suit your own environment:

ComponentBase URL
Web Applicationhttps://localhost:5443
Curity Identity Serverhttps://localhost:8443
APIhttps://localhost:3000

Running the Example

Use the following commands to install Python dependencies and to start listening for web requests on port 5443:

  • pip install -r requirements.txt
  • python app.py

When the code sample starts, the client.py file uses the discovery endpoint of the Curity Identity Server to load metadata describing all of the standard endpoints:

def __init_config(self):

    if 'issuer' in self.config:
        meta_data_url = self.config['issuer'] + '/.well-known/openid-configuration'
        print('Fetching config from: %s' % meta_data_url)
        meta_data = urlopen(meta_data_url, context=self.ctx)
        if meta_data:
            self.config.update(json.load(meta_data))

Test Logins

Then browse to https://localhost:5443 to see the home page of the website, in which the user chooses to sign in:

Web Login

The code implements OpenID Connect standard authentication redirect functionality by first forming a request URL with the standard parameters and redirecting the browser:

def get_authn_req_url(self, session, acr, forceAuthN, scope, forceConsent, allowConsentOptionDeselection,
                          response_type, ui_locales, max_age, claims, send_parameters_via):

    state = tools.generate_random_string()
    session['state'] = state
    session['code_verifier'] = code_verifier = tools.generate_random_string(100)
    session["flow"] = response_type

    code_challenge = tools.base64_urlencode(hashlib.sha256(code_verifier).digest())

    request_args = {'scope': scope,
                    'response_type': response_type,
                    'client_id': self.config['client_id'],
                    'state': state,
                    'code_challenge': code_challenge,
                    'code_challenge_method': "S256",
                    'redirect_uri': self.config.get('redirect_uri', "")}

Upon return, the app receives an authorization code and swaps it for tokens using an authorization code grant message:

def get_token(self, code, code_verifier):
        
    data = {'client_id': self.config['client_id'], "client_secret": self.config['client_secret'],
            'code': code,
            "code_verifier": code_verifier,
            'redirect_uri': self.config['redirect_uri'],
            'grant_type': 'authorization_code'}

    try:
        token_response = self.__urlopen(self.config['token_endpoint'], urlencode(data), context=self.ctx)

Use Access Tokens

The web back end then receives opaque access and refresh tokens, and operations on them can be tested from within the web app:

Python Website

The Python application could verify received tokens and return data directly, though it is more common these days for a web back end to call APIs instead. No API is provided with the sample, but the app.py file demonstrates how to make an outgoing call, by adding a bearer token to the HTTP Authorization Header:

def call_api():

    try:
        req = Request(_config['api_endpoint'])
        req.add_header('User-Agent', 'CurityExample/1.0')
        req.add_header("Authorization", "Bearer %s" % access_token)
        req.add_header("Accept", 'application/json')
        
        if 'subscription_key' in _config:
            req.add_header('Ocp-Apim-Subscription-Key', _config['subscription_key'])
            req.add_header('Ocp-Apim-Trace', 'true')

        response = urlopen(req, context=tools.get_ssl_context(_config))
        user.api_response = {'code': response.code, 'data': response.read()}

To work with a real API you could adapt the Python API Code Sample. For a full end to end solution an API Gateway should also be integrated, using the Phantom Token Pattern.

Logout

Finally the sample can be used to perform a logout operation, and to revoke tokens if required. A post_logout_redirect_uri value is supplied to ensure that the user is returned to the home page when the logout operation completes.

def logout():
    
    if 'session_id' in session:
        del _session_store[session['session_id']]
    session.clear()
    
    print("Logging out at ", _config['end_session_endpoint'])
    logout_request = _config['end_session_endpoint'] + '?client_id=' + _config['client_id'] + '&post_logout_redirect_uri=' + _config['base_url']
    return redirect(logout_request)

Conclusion

We demonstrated the basics of an OpenID Connect flow in Python, resulting in a web application that uses a number of standards based endpoints. This enables features for login, logout and working with tokens, including calls to OAuth secured APIs.

Keep up with our latest articles and how-tos RSS feeds.