Skip to content

Authentication using OAuth and Open ID Connect

If your application is using identity based licensing and the 10Duke Scale IdToken Authorization scheme, you will need to establish an authenticated session with your identity provider (IDP - also known as Authorization Server AS).

This can be achieved via a number of different OAuth authorization grant flows. Which one to use will be determined by the type of application you are developing, your security requirements, and the flows offered by you IDP.

The most likely flows for use are:

  • Authorization grant for web applications
  • Implicit flow grant for single page applications running in a browser
  • Device flow where the device is unable to present a UI for authentication
  • PKCE flow (or device flow) for desktop applications

For the Device Flow and PKCE Flow helpers are included to simplify implementation.

Device Flow

tenduke_core.auth.DeviceFlowClient can be used to implement the Device Flow.

Once the configuration for connection with IDP is loaded, the client can be used to authorize and fetch a token.

As the device flow token fetch involves interaction on another device or another process on the same device, it is expected to take some time. The token polling can be called synchronously (blocking) or asynchronously (non-blocking).

import webbrowser

from tenduke_core.auth import DeviceFlowClient
from tenduke_core.auth.oidc_discovery import load_openid_config
from tenduke_core.config import TendukeConfig

from tenduke_scale.http import ScaleSessionFactory

config = TendukeConfig.load(
    idp_oidc_discovery_url="https://idp.example.com/user/.well-known/openid-configuration",
)
session_factory = ScaleSessionFactory(config, "device-flow-example", "1.0.0")
load_openid_config(session_factory, config)
device_flow_client = DeviceFlowClient(config, session_factory)
# this will call the IDP and get a code plus a URI to poll
response = device_flow_client.authorize()
# interaction with the user needs to be initialised, either display the response.uri and
# response.user_code, or open a web browser with the response.uri or response.uri_complete
if response.uri_complete is not None:
    webbrowser.open(response.uri_complete, new=0, autoraise=True)

    # blocking call, continues once user has authenticated and authorized the application
    device_flow_client.poll_for_token()
    # if this succeeds, the user has been authenticated and the client now has an id_token

The next steps from here would be using the authenticated session to authorize requests to the 10Duke Scale API.

PKCE Flow

tenduke_core.auth.PkceFlowClient can be used to implement the PKCE Flow.

Once the configuration for connection with IDP is loaded, the client can be used to authorize and fetch a token.

If you wish to use the system default browser to perform the authentication with the IDP, then the client has a single method that wraps up the process:

from tenduke_core.auth import PkceFlowClient
from tenduke_core.auth.oidc_discovery import load_openid_config
from tenduke_core.config import TendukeConfig

from tenduke_scale.http import ScaleSessionFactory

config = TendukeConfig.load(
    idp_oidc_discovery_url="https://idp.example.com/user/.well-known/openid-configuration",
)
session_factory = ScaleSessionFactory(config, "pkce-login-with-default-browser-example", "1.0.0")
load_openid_config(session_factory, config)
client = PkceFlowClient(config,session_factory)

client.login_with_default_browser()
# if this succeeds, the user has been authenticated and the client now has an id_token

This implementation uses an internal web server to listen for the redirect request following successful authentication.

By default the tenduke_core.auth.PkceFlowClient listens on a redirect URI of http://localhost/login/callback and a random port.

The URI and port can be configured or the IDP can be configured to redirect to the default specified here.

The client will send a basic HTTP success message stating that login has succeeded and the browser window can be closed. This message can be replaced with you own message via configuration.

If you want to handle the interaction yourself (using a specific browser or for some other reason), then the client provides tenduke_core.auth.PkceFlowClient.create_authorization_url and tenduke_core.auth.PkceFlowClient.fetch_token methods:

from tenduke_core.auth import PkceFlowClient
from tenduke_core.auth.oidc_discovery import load_openid_config
from tenduke_core.config import TendukeConfig

from tenduke_scale.http import ScaleSessionFactory


def perform_authentication():
    """Authentication via (for example) embedded browser."""
    raise NotImplementedError("TODO")


config = TendukeConfig.load(
    idp_oidc_discovery_url="https://idp.example.com/user/.well-known/openid-configuration",
)
session_factory = ScaleSessionFactory(config, "pkce-custom-interaction-example", "1.0.0")
load_openid_config(session_factory, config)
client = PkceFlowClient(config, session_factory)

# In a real application this would be the result of binding to a
# specific or random port and starting to listen.
PORT = 5005

url = client.create_authorization_url(PORT)

# perform authentication, get authorization_response
authorization_response = perform_authentication()

client.fetch_token(authorization_response)