SAML to OAuth2 Bridge Solution
Context & Problem
In a legacy enterprise environment, many identity providers (IdPs) only support SAML (Security Assertion Markup Language) for single sign-on (SSO). However, modern web applications and APIs typically rely on OAuth2 or OIDC (OpenID Connect) for authorization and authentication, using JSON Web Tokens (JWTs).
At WirelessCar, we faced a challenge where we needed to connect a legacy Active Directory (SAML-only) with a modern application that required OAuth2 tokens for Role-Based Access Control (RBAC). The legacy system could not issue JWTs, and the modern application could not parse SAML assertions.
Solution Overview
To bridge this gap, we built a serverless connector using AWS Lambdas and DynamoDB. This solution acts as a mediator, exchanging a valid SAML assertion for a custom OAuth2 token that the downstream application accepts.
Key Components
- SAML Parser Lambda (Node.js):
- Role: Entry point for the SAML assertion.
- Logic: Validates the SAML signature, parses the XML to extract user attributes (UPN, email) and group memberships (roles).
- Output: Saves the normalized user context to DynamoDB with a short Time-To-Live (TTL).
- Context Store (DynamoDB):
- Role: Temporary state store.
- Logic: Holds the user information extracted from SAML. Acts as a secure hand-off point between the parser and the minter.
- Token Minter Lambda (Java):
- Role: OAuth2 Authorization Server (simplified).
- Logic: Retrieved user context from DynamoDB. Maps AD roles to application scopes/claims. Calls internal signing endpoints or uses a keystore to generate a signed JWT.
- Output: Returns the access token to the client.
Architecture
High-Level Flow
The client (browser) performs a SAML login with the IdP, receives an assertion, and posts it to our bridge. The bridge processes it and returns an OAuth2 token.
sequenceDiagram participant User as User/Browser participant IdP as Legacy AD (SAML) participant Parser as SAML Parser Lambda (Node.js) participant DDB as DynamoDB participant Minter as Token Mint Lambda (Java) participant App as Modern App User->>IdP: Authenticate IdP-->>User: SAML Assertion (XML) User->>Parser: POST /login/saml (Assertion) activate Parser Parser->>Parser: Parse & Validate SAML Parser->>Parser: Extract Roles & User Info Parser->>DDB: Save User Context (ID: RequestID) Parser->>Minter: Invoke (RequestID) deactivate Parser activate Minter Minter->>DDB: Get User Context (RequestID) DDB-->>Minter: { User: "...", Roles: [...] } Minter->>Minter: Map Roles to Scopes Minter->>Minter: Generate & Sign JWT Minter-->>User: Return Access Token deactivate Minter User->>App: Request with Bearer Token
Component Diagram
flowchart LR subgraph "External" IdP[Legacy Active Directory] Client[Web Client] end subgraph "AWS Bridge" direction TB Parser["Lambda: SAML Parser<br/>(Node.js + saml2-js)"] Minter["Lambda: Token Minter<br/>(Java)"] DDB[(DynamoDB Table)] end subgraph "Downstream" App[Modern Application] end IdP -->|SAML Assertion| Client Client -->|POST Assertion| Parser Parser -->|1. Parse & Extract| Parser Parser -->|2. Save Context| DDB Parser -->|3. Invoke| Minter Minter -->|4. Read Context| DDB Minter -->|5. Generate Token| Client Client --|Bearer Token|--> App
Detailed Implementation Notes
Node.js SAML Parser
We chose Node.js for the parsing layer because of its excellent ecosystem for handling JSON and XML.
- Library: Used
saml2-js(orpassport-saml) to handle the complex XML parsing and signature verification. - Security: Verifies the IdP’s X.509 certificate to ensure the assertion is authentic.
DynamoDB Storage
- Partition Key: A unique temporary ID (e.g., a one-time code or correlation ID) generated by the Parser.
- Data: Stores
user_principal_name,email, and a list ofad_groups. - TTL: Records are set to expire very quickly (e.g., 5 minutes) to prevent replay attacks or stale data usage.
Java Token Minter
We used Java here likely due to existing internal libraries or strict security compliance requirements for token signing that were already implemented in Java.
- Token Generation: Uses standard libraries (like JJWT or Nimbus JOSE + JWT) to create signed tokens.
- RBAC Mapping: Translates raw AD groups (e.g.,
CN=App_Admins,OU=...) into clean application scopes (e.g.,admin:write,user:read).
Pros & Cons
Pros
- Decoupling: The modern app doesn’t need to know anything about SAML or XML.
- Security: The bridge isolates the legacy protocol. Token generation is centralized.
- Scalability: AWS Lambda handles the load automatically.
Cons
- Latency: Adding two Lambda hops and a DB write adds some latency to the login process.
- Complexity: Managing two separate Lambdas and a state store is more complex than a direct integration.