Amundsen — Integrate with Okta SingleSignOn

Owen Leung
5 min readDec 11, 2022

--

Okta is a cloud-based identity and access management service that provides secure and reliable user authentication for web and mobile applications. It helps organizations manage and control access to their applications, as well as providing single sign-on capabilities for their users. With Okta, organizations can easily and securely manage user access to their applications, enabling their employees to work from anywhere and on any device.

So how do we integrate Amundsen (or some other applications) into Okta ? Let’s find out.

(Notes: I couldn’t get all these stuff working without referencing this amundsen article. Huge credit goes to Verdan Mahmood !)

Step 1 — Create your own Okta Account

First step for us is to set up our own okta account. For this you’d need to visit https://www.okta.com/ and follow their GetStarted instructions. I won’t go into the details there as I think it’s pretty intuitive. At the end you should be able to login to the user console, click the admin button, & arrive at the admin console like below :

User Page
Admin Page

Step 2 — Create your okta OIDC-enabled application

Second step is to create our okta OIDC-enabled application. We choose OIDC here since this is the authentication method that Amundsen supports. So to do this — Go to the applicationtab on your left, click application, and click create app integration , then choose the OIDC option like below :

Now, here comes the fun part. You’d need to check Authorization_code and the implicate(hybrid) box, and also provide a correct sign-in redirect uri and sign-out redirect uri like below :

The most important thing here is to add the /auth suffix for the sign-in redirect uri . Without providing this, authentication wouldn’t take place and you won’t be able to visit amundsen frontend.

In the screenshot above, I’m showing http://localhost:8080 which is where I host my amundsen frontend page. But at a production environment where you’d host Amundsen in a Kubernetes cluster, you should be posting your load balancer address that exposes your k8s cluster. For example, in an AWS environment it might look something like xxxxx.elb.[region-name].amazonaws.com .

Afterwards, you can just click save. And then you should be seeing your client_id and client_secret like below. Pencil down these 2 info as you would need them very soon when deploying Amundsen

Step 3 — Configure the API-Scope

After creating the OIDC-enabled application, you’d need to configure its API scope also. Specifically, you’d need to grant 2 API scopes, which are okta.users.read , and okta.users.read.self . To do this, go to the Okta API Scopes tab, search for okta.users.read and okta.users.read.self , and click Grant

Step 4 — Revise the metadata service to programmatically get user token from okta

This, in my opinion, is the hardest part. The metadata service from Amundsen, by default, doesn’t have the python code to connect to the okta endpoint and fetch user’s token from there. So you’d need to supplement your own code to do this. I can’t guarantee that my code piece below works in all scanerios but I believe it will provide a good starting point for you to work on.

So in the /metadata/metadata_service folder, create a python file get_okta_user.py and post the following code :

from flask import current_app as app
from amundsen_common.models.user import UserSchema
from metadata_service.exception import NotFoundException
from typing import Dict
from metadata_service.proxy import get_proxy_client
import logging

def get_user_from_oidc(user_id: str) -> Dict:
metadata = app.auth_client.load_server_metadata()
search_endpoint = f'{metadata["issuer"]}/api/v1/users?q={user_id}&limit=1'

_not_found_error = f"User Not Found in the OIDC Provider. User ID: {user_id}"

logging.info("Calling app.auth_client.get() to fetch user information from okta")
response = app.auth_client.get(search_endpoint)
response.raise_for_status()
user_info = response.json()
if not user_info:
raise NotFoundException(_not_found_error)
user_data = dict()
_user = user_info[0]

user_data.update(_user["profile"])
user_data.update({
"name": f'{_user["profile"]["firstName"]} {_user["profile"]["lastName"]}'
})
profile_url = _user.get("_links", {}).get("self", {}).get("href")
user_data.update({"profile_url": profile_url})

return {
"user_id": user_id,
"email": user_data["email"],
"first_name": user_data["firstName"],
"last_name": user_data["lastName"],
"full_name": user_data["name"],
"display_name": user_data["name"],
"profile_url": user_data["profile_url"],
}

def get_user_details(user_id: str) -> Dict:
client = get_proxy_client()
schema = UserSchema()
if not hasattr(app, 'auth_client'):
logging.exception("auth_client not available in the current app")

try:
user = client.get_user(id=user_id)
except NotFoundException as e:
logging.info("Original Error : {e}\n\nUser not found in the database. Trying to create one using oidc.get_user_detail")

if user is not None:
return schema.dump(user)
else:
try:
logging.info(f"{user_id} ==> Calling get_user_from_oidc() at get_okta_user.py...")
user_info = get_user_from_oidc(user_id=user_id)

user = schema.load(user_info)

new_user, is_created = client.create_update_user(user=user)
return schema.dump(new_user)

except Exception as ex:
logging.exception(str(ex), exc_info=True)
# Return the required information only
return {
"email": user_id,
"user_id": user_id,
}

Then, in the /metadata/metadata_service/__init__.py file, add the following line inside the create_app() function.

app.config["USER_DETAIL_METHOD"] = get_user_details

Step 5 — Put the Client_ID & Client_Secret into the helm value file

Finally, we need to configure our helm value file and put our client_id and client_secret there. A couple of configurations are also necessary in order for the Amundsen application to use the proper class. Below code snippet specifies the necessary configs that you need to include for integrating with Okta :

flaskApp:
module: "flaskoidc"
class: "FlaskOIDC"

oidc:
enabled: true
frontend:
client_id: []
client_secret: ""
metadata:
client_id: [CLIENT_ID]
client_secret: [CLIENT_SECRET]
search:
client_id: [CLIENT_ID]
client_secret: [CLIENT_SECRET]
configs:
FLASK_OIDC_PROVIDER_NAME: okta
FLASK_OIDC_SCOPES: "openid email profile okta.users.read okta.users.read.self offline_access"
FLASK_OIDC_USER_ID_FIELD: email
FLASK_OIDC_REDIRECT_URI: "/auth"
FLASK_OIDC_CONFIG_URL: "[Your Okta Domain]/.well-known/openid-configuration"
FLASK_OIDC_WHITELISTED_ENDPOINTS: "status,healthcheck,health,api.healthcheck"

frontEnd:
config:
class: amundsen_application.oidc_config.OidcConfig

That’s it ! After doing all these you should be able to view the amundsen frontend page like below !

Thanks for reading. Stay tuned for more contents =)

Owen

--

--