In my previous article, I explained how to use Python’s built-in logging library to send logs directly to the OCI logging service. That was a useful and fun exercise which I’ve used for a dozen small projects since that article was published. I was recently asked how to use the OCI identity management service, OCI IAM Identity Domains, to serve as an OpenID Connect (OIDC) provider for a custom application. It’s not a difficult task, but it helps to have a reference to look at.

In this article we’re going to set up an Identity Domain to act as the provider in the OIDC three-legged flow. This will let us offload storage, protection, and processing of our user identities to OCI rather than having to manage sensitive data ourselves. We will be able to authenticate users and read claims about the user’s identity returned in a JSON Web Token (JWT) consumed by our application. Scopes will be defined to access additional information about the users, such as verified email addresses. Implementing OCI Identity Domains as our IdP (Identity Provider) allows access to other features if we decide to use them, such as Adaptive Security, Passwordless Authentication, or Network Perimeters.

To create our application we will be using the popular Python web framework Flask. This will handle routing, session handling, and other minutae of web development so we can focus on the fun parts of the application. We will use the popular authentication library Authlib to perform the hard tasks about OIDC for us, like claim checking and signature verification. Without this library, we would need to implement these features ourselves. Please verify that the licenses for each library work for your project. With the exception of the frameworks and libraries previously mentioned, I will try to make this tutorial as generic as I’m able for those using different languages and libraries.


OCI IAM Identity Domain

Confidential Application Configuration

The OIDC provider endpoint and client will depend on the Identity Domain. Either navigate to an existing domain that will be our identity provider, or create a new domain for the application.

Note: This example utilizes the flask development server. For production you would use URLs to your web server or load balancer.

  1. Open Integrated Applications from the Identity Domain menu and add a new Confidential Application
  2. Give the applicaiton an appropriate name
  3. In Configure OAuth, select Configure this application as a resource server now and enter http://localhost:5000 for Primary audience to work with the local Flask development server
  4. In the Configure OAuth step select Configure this application as a client now to access the OAuth configurations
  5. Check the Client Credentials and Authorization Code grants
  6. Check Allow non-HTTPS URLs and enter http://localhost:5000/callback as Redirect URL
  7. Click Next and Finish then finally Activate to go live with the application

Add application selector

Resource server configuration box

Client configuration box

Active application

Once we have an active application, we will need three pieces of information:

  1. The Client ID which can be found in the Integrated Application, in OAuth Configuration under General Information
  2. The Client Secret which can be accessed directly below the Client ID
  3. The Domain URL, which is in the Domain Information on the Overview page for the Domain

App OAuth configurations

Domain info

Set the Issuer on the Identity Domain

Some applications require that the OpenID Connect Discovery document match the issuer in returned tokens. By default, the URL in the issuer field on JWTs is https://identity.oraclecloud.com/, which does not identify the specific domain issuing tokens. This shouldn’t affect our Flask application, but if you’re using another framework or service it may be required to change the issuer.

The documentation on how to change the OAuth issuer for the domain can be found here.

Ensure the Domain Signing Certificate is Accessible

In Settings > Domain settings, make sure the Configure client access is checked under Access signing certificate.

Signing certificate setting


Python Flask Application

Install Dependencies

Python best practice is to set up an independent virtual environment for projects like these to prevent dependency conflicts. Let’s do that now:

python3 -m venv oidc-app

source oidc-app/bin/activate

We now have a Python environment for our flask app. Now we install dependencies.

pip install Flask authlib

We should be ready to begin coding. Create a file named wsgi.py and open it in your editor of choice.

Create the Flask Application

from flask import Flask, session, redirect, render_template, url_for, request
app = Flask(__name__)
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' # Needed for cross-domain redirects

We have created our application and assigned it the app variable as well as setting the session SameSite attribute to Lax.

Configure the Authlib Flask Object

We now need to define the authentication parameters for the OAuth 2.0 implementation. This will require the domain endpoint, client ID, and client secret we recorded earlier.

from authlib.integrations.flask_client import OAuth
oauth = OAuth(app)
oauth.register(
    'ocidomain',
    client_id=getenv('1234567'), # Confidential application client ID
    client_secret=getenv('abcdefg'), # Confidential application client secret
    server_metadata_url='https://idcs-xyz.identity.oraclecloud.com/.well-known/openid-configuration',
    client_kwargs={'scope': 'openid'}) # Note the scope is defined here

We now have our OAuth implementation specifically for Flask. Next, we need to define a place to initiate the OAuth flow.

Define OAuth Login Routes

@app.route('/login', methods=['GET'])
def login():
    uri = url_for('callback', _external=True)
    return oauth.ocidomain.authorize_redirect(uri)

We define the /login path to redirect the user to the authorization endpoint. The /callback endpoint is constructed to be passed as an argument for the redirect_uri parameter. The end result is the user being redirected to a URL similar to:

https://idcs-xyz.identity.oraclecloud.com/oauth2/v1/authorize?client_id=1234567&response_type=code&redirect_uri=app.com/callback&scope=openid&nonce=random&state=random

At this URL the user will be challanged for their credentials and redirected to the redirect_uri, which is our callback path:

@app.route('/callback', methods=['GET'])
def callback():
    token = oauth.ocidomain.authorize_access_token()
    session['user'] = token["userinfo"]["sub"]
    return redirect(url_for('home'))

Finally we have our JWT assigned to the token variable. We set the session to the use the sub attribute of the userinfo object as the user. You may want to define the user differently, which you should to fit your use case. We then send the user back to our home URL with their active session.

Now we will set our logout endpoint to revoke the user session on logout.

@app.route('/logout', methods=[HTTPMethod.GET])
def logout():
    url = f'{idm_host}/oauth2/v1/userlogout?id_token_hint={session.get("id_token")}'
    url += f'&post_logout_redirect_uri={app_host}{url_for("home")}'
    session.clear()
    return redirect(url)

Test the Flow

Once we’ve put together all the pieces, we can test the authentication flow.

flask run

In this example, we start at our homepage located at localhost:5000/

Page without active session

Clicking on Sign In takes us to localhost:5000/login which redirects us to a GET request to the following URL:

https://idcs-abcdxyz.identity.oraclecloud.com/oauth2/v1/authorize?client_id=123456abcdef&response_type=code&redirect_uri=http://localhost:5000/callback&scope=openid&nonce=random_value&state=random_value
 
We are then presented with the Identity Domain sign in page:

Sign in prompt

 

 
Once we have entered our credentials, we are redirected back to the callback URL at:
 
http://localhost:5000/callback?code=abcd123&state=random_value

We are now logged into the application.

Active session


To Conclude

When it comes to user identity management, like a board game at NORAD, the only winning move is not to play. Let Oracle Cloud manage user identites for  you. Using OCI IAM Identity Domains, you can have an OIDC provider configured quickly and easily.

Want to change the sign in page for your organization? The next step would be to re-brand the sign-in as your own, which can be done with ease in OCI.

Happy building!