SAML 2.0 / Enterprise SSO
Configure SAML 2.0 identity providers — Okta, Azure AD, Google Workspace — and map SAML attributes to JWT claims for use in policy evaluation.
SAML 2.0 / Enterprise SSO
Phase note. This feature ships in Phase 2. The API shown is the planned surface.
CoreSDK will support SAML 2.0 for enterprise identity providers alongside its native OIDC/JWT pipeline. SAML assertions are validated, mapped to JWT claims, and injected into the request context — your Rego policies and handlers see a consistent input shape regardless of whether the identity came from OIDC or SAML.
In Phase 1, CoreSDK validates tokens already issued by your IdP. If your IdP federates SAML to an OIDC/OAuth layer (which Okta, Azure AD, and Google Workspace all support), you can use JWT Authentication today with no SAML-specific code.
How SAML fits into the auth pipeline
Browser / Client
↓
[1] SP-initiated login → CoreSDK generates AuthnRequest → IdP
↓
[2] IdP authenticates user → sends SAMLResponse to ACS endpoint
↓
[3] CoreSDK validates assertion (signature, audience, NotOnOrAfter)
↓
[4] Attribute mapping: SAML attributes → JWT claims
↓
[5] Short-lived JWT issued → attached to session / returned to client
↓
[6] All subsequent requests use the JWT — auth middleware, policy eval,
and request context work identically to OIDC flowsConfiguring the service provider (SP) (Phase 2)
use coresdk_engine::{Engine, EngineConfig, saml::{SamlConfig, AttributeMapping}};
use std::env;
let engine = Engine::new(EngineConfig {
tenant_id: "acme".to_string(),
saml: Some(SamlConfig {
entity_id: "https://app.example.com".to_string(),
acs_url: "https://app.example.com/auth/saml/acs".to_string(),
idp_metadata_url: "https://your-idp.okta.com/app/abc123/sso/saml/metadata".to_string(),
clock_skew_seconds: 60,
attribute_mapping: AttributeMapping {
email: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress".to_string(),
role: "role".to_string(),
tenant: "tenant_id".to_string(),
},
sp_private_key_pem: env::var("SAML_SP_PRIVATE_KEY").ok(),
sp_certificate_pem: env::var("SAML_SP_CERTIFICATE").ok(),
}),
..Default::default()
})?;from coresdk import CoreSDKClient, SDKConfig
from coresdk.saml import SamlConfig, AttributeMapping
import os
_sdk = CoreSDKClient(SDKConfig(
sidecar_addr="http://127.0.0.1:7233",
tenant_id="acme",
service_name="my-app",
saml=SamlConfig(
entity_id="https://app.example.com",
acs_url="https://app.example.com/auth/saml/acs",
idp_metadata_url="https://your-idp.okta.com/app/abc123/sso/saml/metadata",
clock_skew_seconds=60,
attribute_mapping=AttributeMapping(
email="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
role="role",
tenant="tenant_id",
),
sp_private_key_pem=os.getenv("SAML_SP_PRIVATE_KEY"),
sp_certificate_pem=os.getenv("SAML_SP_CERTIFICATE"),
),
))// Phase 2 — Go SDK ships in Phase 2.
import (
"os"
sdk "github.com/coresdk-dev/sdk-go"
"github.com/coresdk-dev/sdk-go/saml"
)
s := sdk.New(sdk.Config{
SidecarAddr: "http://127.0.0.1:7233",
TenantID: "acme",
SAML: &saml.Config{
EntityID: "https://app.example.com",
ACSURL: "https://app.example.com/auth/saml/acs",
IDPMetadataURL: "https://your-idp.okta.com/app/abc123/sso/saml/metadata",
ClockSkewSeconds: 60,
AttributeMapping: saml.AttributeMapping{
Email: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
Role: "role",
Tenant: "tenant_id",
},
SPPrivateKeyPEM: os.Getenv("SAML_SP_PRIVATE_KEY"),
SPCertificatePEM: os.Getenv("SAML_SP_CERTIFICATE"),
},
})// Phase 2 — TypeScript SDK ships in Phase 2.
import { CoreSDKClient } from "@coresdk/node";
const sdk = new CoreSDKClient({
sidecarAddr: "http://127.0.0.1:7233",
tenantId: "acme",
saml: {
entityId: "https://app.example.com",
acsUrl: "https://app.example.com/auth/saml/acs",
idpMetadataUrl: "https://your-idp.okta.com/app/abc123/sso/saml/metadata",
clockSkewSeconds: 60,
attributeMapping: {
email: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
role: "role",
tenant: "tenant_id",
},
spPrivateKeyPem: process.env.SAML_SP_PRIVATE_KEY,
spCertificatePem: process.env.SAML_SP_CERTIFICATE,
},
});IdP configuration
In Okta Admin Console → Applications → Create App Integration → SAML 2.0:
| Field | Value |
|---|---|
| Single sign-on URL | https://app.example.com/auth/saml/acs |
| Audience URI (SP Entity ID) | https://app.example.com |
| Name ID format | EmailAddress |
Under Attribute Statements:
| Name | Value |
|---|---|
role | user.role |
tenant_id | user.department |
Copy the IdP Metadata URL from Sign On → View SAML setup instructions.
In Azure Portal → Enterprise Applications → New Application → Non-gallery → Single sign-on → SAML:
| Field | Value |
|---|---|
| Identifier (Entity ID) | https://app.example.com |
| Reply URL (ACS) | https://app.example.com/auth/saml/acs |
Under Attributes & Claims:
| Claim | Source |
|---|---|
role | user.assignedroles |
tenant_id | user.companyname |
Copy the App Federation Metadata Url as your idp_metadata_url.
Mounting SAML endpoints (Phase 2)
use axum::Router;
use coresdk_engine::saml::saml_router;
let app = Router::new()
.nest("/auth/saml", saml_router(engine.clone()))
// GET /auth/saml/login — redirects to IdP
// POST /auth/saml/acs — validates assertion, issues JWT
// GET /auth/saml/metadata — SP metadata XML
.route("/api/orders", axum::routing::get(orders_handler));from fastapi import FastAPI
from coresdk.saml import saml_router
app = FastAPI()
app.include_router(saml_router(_sdk), prefix="/auth/saml")// Phase 2 — Go SDK ships in Phase 2.
import "github.com/coresdk-dev/sdk-go/saml"
mux := http.NewServeMux()
saml.Mount(mux, "/auth/saml", s)
mux.HandleFunc("/api/orders", ordersHandler)// Phase 2 — TypeScript SDK ships in Phase 2.
import express from "express";
import { samlRouter } from "@coresdk/node/saml";
const app = express();
app.use("/auth/saml", samlRouter(sdk));Attribute mapping in Rego
After assertion exchange, SAML and OIDC users are indistinguishable in policies:
package coresdk.authz
allow {
input.action == "orders:read"
input.user.role == "member"
input.user.tenant_id == input.resource.tenant
}
# Custom SAML attributes land in input.user.claims
allow {
input.action == "reports:read"
input.user.claims.department == "finance"
}Testing SAML locally
# Generate a signed test assertion (no real IdP needed)
coresdk saml test-assertion \
--email alice@example.com \
--role member \
--tenant acme \
--output /tmp/assertion.xml
# POST to local ACS
curl -X POST http://localhost:8080/auth/saml/acs \
-d "SAMLResponse=$(base64 -w0 /tmp/assertion.xml)"Set CORESDK_ENV=development to skip assertion signature validation locally.
Next steps
- JWT Authentication — OIDC/JWT pipeline SAML integrates with
- SCIM Provisioning — automate user provisioning from your IdP
- RBAC & Roles — map SAML roles to policy rules