OpenID Connect authentication for CLI applications

In modern application ecosystems, ensuring secure authentication and authorization is paramount. Most web applications rely on OpenID Connect (OIDC) as a simple but robust way to secure an application or service. In this example, I'll show you how to implement OIDC-based SSO for CLI applications interacting with sensitive APIs.

TL;DR

Auth process overview

The sequence diagram outlines the authentication process. Here’s a step-by-step breakdown:

  1. User Initiates Authentication. The user runs cli-app auth in their terminal. The “auth” argument is needed to trigger the authentication process explicitly.
  2. Exchange Code Generation. The CLI app communicates with the API Gateway to generate a unique exchange code. This code is essential for securely linking the CLI application, the user’s browser session and the OIDC auth flow.
  3. User Action in Browser. The CLI app presents the exchange code along with a URL, instructing the user to open a browser and navigate to the provided link. This URL directs the user to the API Gateway.
  4. OIDC Provider Interaction. Upon visiting the link, the user is redirected to the OIDC provider’s authentication page. The user completes the login process, which includes providing credentials and verifying their identity (by second factor for example).
  5. Authentication Flow Completion. After successful authentication, the OIDC provider redirects the user back to the API Gateway, appending it with an authorization code that will be used for token exchange.
  6. JWT Exchange. The API Gateway uses the provided authorization code to request a JWT (JSON Web Token). This JWT is then made accessible to the CLI app.
  7. Secure Session Establishment. The CLI app retrieves the JWT and then uses it for signing each request to establish a secure session for subsequent interactions with private APIs.

Advantages of the Flow

Security: The exchange code ensures that the CLI app cannot directly obtain a JWT without user involvement, preventing unauthorized access.
User-Friendly: By leveraging the browser for authentication, the flow ensures familiarity and reduces friction for the user.
Standardized: OIDC’s standardized approach guarantees compatibility with a wide range of identity providers.

Under the hood

API Gateway represents a Python application based on top of FastAPI and Authlib.
API protection is implemented by simple middleware injected into the protected router and mounted to the main application

1
2
3
4
5
6
7
8
app = FastAPI()
app.add_middleware(SessionMiddleware, secret_key=Config.session_secret)
app.include_router(auth_device.router)
app.include_router(auth_web.router)
app_api = FastAPI()
app_api.add_middleware(AccessValidatorMiddleware)
app_api.include_router(api.router)
app.mount("/", app_api)

In this custom middleware, I do several validations:

  • JWT validation
  • Check for presenting some required claims, group membership in my example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class AccessValidatorMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
try:
token = await oauth2_scheme(request)
user = await get_jwk_key(token)
if not any(
group in user.get("groups", []) for group in Config.oidc_allowed_groups
):
logger.error(f"User: {user.get('name')}")
raise HTTPException(
status_code=403,
detail="User does not have access to this resource",
)
logger.info(f"User: {user.get('name')}")
response = await call_next(request)
return response
except HTTPException as e:
return JSONResponse(
status_code=e.status_code,
content={"message": e.detail},
)
except Exception as e:
raise Exception(e)

This is add low overhead, but provide simple and reliable way to expose private API.

Side effects

JWT protection works perfectly not only for CLI applications, you can use it for protection web applications (for BFF for example) or for micro-services communications.
To expand this example you can run React front-end application and ensure that you unable to interact with protected API until you authorized, and implementation authorization is simple and straightforward.

Outcome

OIDC offers a streamlined and secure way to handle CLI authentication. By decoupling user credentials from the CLI app and leveraging JWTs, this flow ensures robust security while maintaining user convenience. For more details, check out the GitHub repository and the sequence diagram.

OIDC CLI SSO Demo

First run of cli-app

cli-app calls gateway, open browser automatically, receive JWT after successfully authorization and call to private API hello

cli-app screen cast

api gateway logs

Call to prohibited resources

cli-app with valid JWT token calls to private API hello, but JWT failed with required claims in token, and denied in access to API

cli-app screen cast

api gateway logs

Expired, broken or invalid JWT

call to private api with expired, broken or invalid JWT with subsequent automatic re-authentication

cli-app screen cast

api gateway logs