Skip to main content
CoreSDK
Authentication

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 flows

Configuring 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:

FieldValue
Single sign-on URLhttps://app.example.com/auth/saml/acs
Audience URI (SP Entity ID)https://app.example.com
Name ID formatEmailAddress

Under Attribute Statements:

NameValue
roleuser.role
tenant_iduser.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:

FieldValue
Identifier (Entity ID)https://app.example.com
Reply URL (ACS)https://app.example.com/auth/saml/acs

Under Attributes & Claims:

ClaimSource
roleuser.assignedroles
tenant_iduser.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

On this page