> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# SSO (OIDC + SAML + LDAP)

> Enterprise OIDC and SAML single sign-on, plus the LDAP direct-bind preview, using the Grantex Go SDK.

## Overview

The `SSO` service manages OIDC and SAML 2.0 enterprise connections,
domain-based enforcement, JIT provisioning, and sessions. It also exposes the
current LDAP direct-bind preview; that preview is not a general LDAP directory
integration and has the limitations documented below.

```go theme={null}
conn, err := client.SSO.CreateConnection(ctx, grantex.CreateSsoConnectionParams{
    Name:            "Okta Production",
    Protocol:        "oidc",
    IssuerURL:       "https://mycompany.okta.com",
    ClientID:        "your-client-id",
    ClientSecret:    "your-client-secret",
    Domains:         []string{"mycompany.com"},
    JitProvisioning: true,
})
```

***

## Enterprise SSO Connections

### Create Connection

Create a new SSO identity provider connection. You can create multiple connections for different domains or providers.

```go theme={null}
conn, err := client.SSO.CreateConnection(ctx, grantex.CreateSsoConnectionParams{
    Name:            "Okta Production",
    Protocol:        "oidc",
    IssuerURL:       "https://mycompany.okta.com",
    ClientID:        "your-okta-client-id",
    ClientSecret:    "your-okta-client-secret",
    Domains:         []string{"mycompany.com", "mycompany.org"},
    JitProvisioning: true,
    DefaultRole:     "member",
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("ID: %s\n", conn.ID)                    // sso_conn_01HX...
fmt.Printf("Name: %s\n", conn.Name)                // Okta Production
fmt.Printf("Protocol: %s\n", conn.Protocol)        // oidc
fmt.Printf("Status: %s\n", conn.Status)            // active
fmt.Printf("Domains: %v\n", conn.Domains)          // [mycompany.com mycompany.org]
fmt.Printf("JIT: %v\n", conn.JitProvisioning)      // true
fmt.Printf("Created: %s\n", conn.CreatedAt)        // 2026-03-29T12:00:00Z
```

#### SAML example

```go theme={null}
samlConn, err := client.SSO.CreateConnection(ctx, grantex.CreateSsoConnectionParams{
    Name:                        "Azure AD SAML",
    Protocol:                    "saml",
    MetadataURL:                 "https://login.microsoftonline.com/.../federationmetadata.xml",
    AssertionConsumerServiceURL: "https://yourapp.com/sso/saml/callback",
    EntityID:                    "https://yourapp.com/saml/metadata",
    Domains:                     []string{"contoso.com"},
    JitProvisioning:             true,
    AttributeMapping: map[string]string{
        "email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
        "name":  "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/displayname",
    },
})
```

### `CreateSsoConnectionParams`

| Field | Type | Required | Description |
| - | - | - | - |
| `Name` | `string` | Yes | A human-readable name for this connection. |
| `Protocol` | `string` | Yes | `"oidc"`, `"saml"`, or `"ldap"`. |
| `IssuerURL` | `string` | OIDC only | The OIDC issuer URL. |
| `ClientID` | `string` | OIDC only | OAuth 2.0 client ID from your IdP. |
| `ClientSecret` | `string` | OIDC only | OAuth 2.0 client secret from your IdP. |
| `MetadataURL` | `string` | SAML only | The SAML metadata URL. |
| `AssertionConsumerServiceURL` | `string` | SAML only | The SAML ACS URL. |
| `EntityID` | `string` | SAML only | The SAML service provider entity ID. |
| `Domains` | `[]string` | No | Email domains to associate with this connection. |
| `JitProvisioning` | `bool` | No | Enable just-in-time user provisioning. |
| `DefaultRole` | `string` | No | Default role for JIT-provisioned users. |
| `AttributeMapping` | `map[string]string` | No | Custom attribute mapping for SAML assertions. |

### `SsoConnection`

| Field | Type | Description |
| - | - | - |
| `ID` | `string` | Unique connection identifier. |
| `Name` | `string` | The connection display name. |
| `Protocol` | `string` | `"oidc"`, `"saml"`, or `"ldap"`. |
| `Status` | `string` | `"active"`, `"inactive"`, or `"error"`. |
| `Domains` | `[]string` | Associated email domains. |
| `JitProvisioning` | `bool` | Whether JIT provisioning is enabled. |
| `CreatedAt` | `string` | ISO 8601 creation timestamp. |
| `UpdatedAt` | `string` | ISO 8601 last-updated timestamp. |

> **Note:** The `ClientSecret` is never returned in responses. It is stored securely on the server.

***

### List Connections

List all SSO connections for your organization.

```go theme={null}
resp, err := client.SSO.ListConnections(ctx)
if err != nil {
    log.Fatal(err)
}

for _, conn := range resp.Connections {
    fmt.Printf("%s (%s) - %s\n", conn.Name, conn.Protocol, conn.Status)
    fmt.Printf("  Domains: %v\n", conn.Domains)
}
```

***

### Get Connection

Retrieve a single SSO connection by ID.

```go theme={null}
conn, err := client.SSO.GetConnection(ctx, "sso_conn_01HX...")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Name: %s\n", conn.Name)         // Okta Production
fmt.Printf("Protocol: %s\n", conn.Protocol) // oidc
fmt.Printf("Status: %s\n", conn.Status)     // active
```

***

### Update Connection

Update an existing SSO connection.

```go theme={null}
updated, err := client.SSO.UpdateConnection(ctx, "sso_conn_01HX...", grantex.UpdateSsoConnectionParams{
    Name:            "Okta Production (updated)",
    JitProvisioning: grantex.Bool(false),
    Domains:         []string{"mycompany.com", "mycompany.org", "subsidiary.com"},
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Name: %s\n", updated.Name)      // Okta Production (updated)
fmt.Printf("Domains: %v\n", updated.Domains) // [mycompany.com mycompany.org subsidiary.com]
```

### `UpdateSsoConnectionParams`

| Field | Type | Description |
| - | - | - |
| `Name` | `string` | Updated display name. |
| `Domains` | `[]string` | Updated list of associated email domains. |
| `JitProvisioning` | `*bool` | Enable or disable JIT provisioning. |
| `DefaultRole` | `string` | Updated default role for JIT-provisioned users. |
| `AttributeMapping` | `map[string]string` | Updated SAML attribute mapping. |

***

### Delete Connection

Delete an SSO connection. Users associated with this connection will no longer be able to log in via SSO.

```go theme={null}
err := client.SSO.DeleteConnection(ctx, "sso_conn_01HX...")
if err != nil {
    log.Fatal(err)
}
// Connection is removed
```

> **Warning:** Deleting a connection immediately disables SSO login for all users routed through it. Ensure you have an alternative authentication method configured before removing a connection.

***

### Test Connection

Test an SSO connection to verify that the IdP configuration is correct and reachable.

```go theme={null}
test, err := client.SSO.TestConnection(ctx, "sso_conn_01HX...")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Success: %v\n", test.Success)           // true
fmt.Printf("Message: %s\n", test.Message)            // Connection verified successfully
fmt.Printf("Response time: %dms\n", test.ResponseTime) // 142
```

### `SsoTestResult`

| Field | Type | Description |
| - | - | - |
| `Success` | `bool` | Whether the connection test passed. |
| `Message` | `string` | Human-readable result message. |
| `ResponseTime` | `int` | IdP response time in milliseconds. |

***

## Enforcement

### Set Enforcement

Enforce SSO login for your organization. When enabled, all members must authenticate through an SSO connection.

```go theme={null}
enforcement, err := client.SSO.SetEnforcement(ctx, grantex.SsoEnforcementParams{
    Enforce: true,
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Enforced: %v\n", enforcement.Enforce)       // true
fmt.Printf("Developer: %s\n", enforcement.DeveloperID)
```

### `SsoEnforcementParams`

| Field | Type | Required | Description |
| - | - | - | - |
| `Enforce` | `bool` | Yes | Whether SSO login is enforced for all members. |

### `SsoEnforcementResponse`

| Field | Type | Description |
| - | - | - |
| `Enforce` | `bool` | Whether SSO enforcement is active. |
| `DeveloperID` | `string` | Organization/developer ID updated by the request. |

***

## Session Management

### List Sessions

List active SSO sessions for your organization.

```go theme={null}
sessions, err := client.SSO.ListSessions(ctx)
if err != nil {
    log.Fatal(err)
}

for _, s := range sessions.Sessions {
    fmt.Printf("%s - %s - expires %s\n", s.Email, s.ConnectionID, s.ExpiresAt)
}
```

### `SsoSession`

| Field | Type | Description |
| - | - | - |
| `ID` | `string` | Session identifier. |
| `DeveloperID` | `string` | Organization/developer that owns the session. |
| `Email` | `string` | The user's email address. |
| `ConnectionID` | `string` | The SSO connection used for this session. |
| `CreatedAt` | `string` | ISO 8601 session creation timestamp. |
| `ExpiresAt` | `string` | ISO 8601 session expiration timestamp. |

***

### Revoke Session

Revoke an active SSO session, forcing the user to re-authenticate.

```go theme={null}
err := client.SSO.RevokeSession(ctx, "sso_sess_01HX...")
if err != nil {
    log.Fatal(err)
}
// Session is revoked
```

***

## Enterprise Login Flow

### Get Login URL (enterprise)

Get the SSO authorization URL for an organization, with an optional email-domain
hint used to select a configured connection.

```go theme={null}
login, err := client.SSO.GetLoginURL(ctx, "org_01HXYZ...", "mycompany.com")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Redirect to: %s\n", login.AuthorizeURL)
```

### Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `org` | `string` | Yes | Organization/developer ID that owns the SSO connections. |
| `domain` | `string` | No | Optional email domain used to select a connection. |

### `SsoLoginResponse` (enterprise)

| Field | Type | Description |
| - | - | - |
| `AuthorizeURL` | `string` | The full authorization URL. Redirect the user here. |

***

### Handle OIDC Callback

Handle the callback from an OIDC identity provider. Exchanges the authorization code for user information and provisions the user if JIT is enabled.

```go theme={null}
result, err := client.SSO.HandleOidcCallback(ctx, grantex.SsoOidcCallbackParams{
    Code:        "oidc_auth_code",
    State:       "csrf_state",
    RedirectURI: "https://yourapp.com/sso/callback", // optional
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Email: %s\n", *result.Email)              // alice@mycompany.com
fmt.Printf("Name: %s\n", *result.Name)                // Alice Smith
fmt.Printf("Subject: %s\n", *result.Sub)              // okta|abc123
fmt.Printf("Developer ID: %s\n", result.DeveloperID)  // dev_01HXYZ...
fmt.Printf("Session: %s\n", result.SessionID)
fmt.Printf("Scopes: %v\n", result.MappedScopes)
```

### `SsoOidcCallbackParams`

| Field | Type | Required | Description |
| - | - | - | - |
| `Code` | `string` | Yes | The authorization code from the OIDC callback. |
| `State` | `string` | Yes | The state parameter for CSRF protection. |
| `RedirectURI` | `string` | No | Redirect URI used during the authorization request. Serialized as `redirect_uri`. |

### `SsoCallbackResult`

| Field | Type | Description |
| - | - | - |
| `Email` | `*string` | The user's email address from the IdP. |
| `Name` | `*string` | The user's display name from the IdP. |
| `Sub` | `*string` | The user's subject identifier from the IdP. |
| `Groups` | `[]string` | Groups returned by the identity provider. |
| `MappedScopes` | `[]string` | Scopes produced by group/default-scope mapping. |
| `PrincipalID` | `*string` | JIT-provisioned or matched principal ID, when available. |
| `DeveloperID` | `string` | The Grantex developer ID. |
| `SessionID` | `string` | Created SSO session ID. |
| `ExpiresAt` | `string` | Session expiration timestamp. |

***

### Handle SAML Callback

Handle the callback from a SAML 2.0 identity provider. Validates the SAML assertion and provisions the user if JIT is enabled.

```go theme={null}
result, err := client.SSO.HandleSamlCallback(ctx, grantex.SsoSamlCallbackParams{
    SAMLResponse: samlResponseValue,
    RelayState:   relayStateValue,
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Email: %s\n", *result.Email)              // bob@contoso.com
fmt.Printf("Name: %s\n", *result.Name)                // Bob Jones
fmt.Printf("Developer ID: %s\n", result.DeveloperID)  // dev_01HXYZ...
fmt.Printf("Session: %s\n", result.SessionID)
fmt.Printf("Scopes: %v\n", result.MappedScopes)
```

### `SsoSamlCallbackParams`

| Field | Type | Required | Description |
| - | - | - | - |
| `SAMLResponse` | `string` | Yes | The base64-encoded SAML response from the IdP. |
| `RelayState` | `string` | No | The RelayState parameter from the SAML callback. |

Returns the same `SsoCallbackResult` as `HandleOidcCallback()`.

***

### Handle LDAP Callback

Authenticate a user with the LDAP direct-bind preview. Unlike OIDC and SAML,
credentials are submitted directly. The built-in client binds a service account,
constructs a user DN from `LdapSearchFilter` plus `LdapSearchBase`, and binds that
DN with the submitted password. It does not search entries, read attributes, or
retrieve group memberships.

```go theme={null}
result, err := client.SSO.HandleLdapCallback(ctx, grantex.SsoLdapCallbackParams{
    Username:     "alice",
    Password:     "user-password",
    ConnectionID: "sso_conn_03HX...",
    Org:          "org_01HXYZ...",
})
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Groups: %v\n", result.Groups)              // []
fmt.Printf("Mapped scopes: %v\n", result.MappedScopes) // default scopes only
fmt.Printf("Developer ID: %s\n", result.DeveloperID)   // dev_01HXYZ...
fmt.Printf("Session: %s\n", result.SessionID)          // ssosess_01HXYZ...
```

### `SsoLdapCallbackParams`

| Field | Type | Required | Description |
| - | - | - | - |
| `Username` | `string` | Yes | Username interpolated into the configured filter to construct the user DN; the built-in preview does not search. |
| `Password` | `string` | Yes | The user's LDAP password for bind authentication. |
| `ConnectionID` | `string` | Yes | The SSO connection ID for the LDAP directory. |
| `Org` | `string` | Yes | Organization/developer ID that owns the LDAP connection. |

Returns `SsoCallbackResult`. With the built-in LDAP client, `Groups` is empty,
`Name` and `Email` are absent, and `MappedScopes` therefore contains only
configured default scopes.

> **Note:** LDAP credentials are never stored by Grantex. They are used only for
> the bind operations and discarded after authentication. LDAP search,
> attribute retrieval, group lookup/mapping, and broad provider compatibility
> are not supported by the built-in preview client.

***

## Full Enterprise SSO Flow Example

```go theme={null}
package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/mishrasanjeev/grantex-go"
)

func main() {
    ctx := context.Background()
    client := grantex.NewClient(os.Getenv("GRANTEX_API_KEY"))

    // Step 1: Create SSO connections (one-time setup)
    _, err := client.SSO.CreateConnection(ctx, grantex.CreateSsoConnectionParams{
        Name:            "Okta Production",
        Protocol:        "oidc",
        IssuerURL:       os.Getenv("OKTA_ISSUER_URL"),
        ClientID:        os.Getenv("OKTA_CLIENT_ID"),
        ClientSecret:    os.Getenv("OKTA_CLIENT_SECRET"),
        Domains:         []string{"mycompany.com"},
        JitProvisioning: true,
        DefaultRole:     "member",
    })
    if err != nil {
        log.Fatal(err)
    }

    _, err = client.SSO.CreateConnection(ctx, grantex.CreateSsoConnectionParams{
        Name:                        "Azure AD SAML",
        Protocol:                    "saml",
        MetadataURL:                 os.Getenv("AZURE_METADATA_URL"),
        AssertionConsumerServiceURL: "https://yourapp.com/sso/saml/callback",
        EntityID:                    "https://yourapp.com/saml/metadata",
        Domains:                     []string{"contoso.com"},
        JitProvisioning:             true,
    })
    if err != nil {
        log.Fatal(err)
    }

    // Step 2: Enforce SSO for the organization
    _, err = client.SSO.SetEnforcement(ctx, grantex.SsoEnforcementParams{
        Enforce: true,
    })
    if err != nil {
        log.Fatal(err)
    }

    // Step 3: Redirect user to SSO login based on email domain
    http.HandleFunc("/sso/login", func(w http.ResponseWriter, r *http.Request) {
        domain := r.URL.Query().Get("domain")
        login, err := client.SSO.GetLoginURL(ctx, "org_01HXYZ...", domain)
        if err != nil {
            http.Error(w, err.Error(), 500)
            return
        }
        http.Redirect(w, r, login.AuthorizeURL, http.StatusFound)
    })

    // Step 4a: Handle OIDC callback
    http.HandleFunc("/sso/callback", func(w http.ResponseWriter, r *http.Request) {
        result, err := client.SSO.HandleOidcCallback(ctx, grantex.SsoOidcCallbackParams{
            Code:        r.URL.Query().Get("code"),
            State:       r.URL.Query().Get("state"),
            RedirectURI: "https://yourapp.com/sso/callback",
        })
        if err != nil {
            http.Error(w, err.Error(), 500)
            return
        }

        fmt.Printf("Welcome, %s (%s)\n", *result.Name, *result.Email)
        if result.PrincipalID != nil {
            fmt.Println("New user provisioned via JIT")
        }
        http.Redirect(w, r, "/dashboard", http.StatusFound)
    })

    // Step 4b: Handle SAML callback
    http.HandleFunc("/sso/saml/callback", func(w http.ResponseWriter, r *http.Request) {
        r.ParseForm()
        result, err := client.SSO.HandleSamlCallback(ctx, grantex.SsoSamlCallbackParams{
            SAMLResponse: r.FormValue("SAMLResponse"),
            RelayState:   r.FormValue("RelayState"),
        })
        if err != nil {
            http.Error(w, err.Error(), 500)
            return
        }

        fmt.Printf("Welcome, %s (%s)\n", *result.Name, *result.Email)
        http.Redirect(w, r, "/dashboard", http.StatusFound)
    })

    log.Fatal(http.ListenAndServe(":8080", nil))
}
```

***

## Legacy Single-Config Methods

> **Note:** The following methods manage a single OIDC configuration per organization. They are retained for backward compatibility. For new integrations, use the enterprise connection methods above which support multiple IdPs, SAML, and domain-based routing.

### Create Config

```go theme={null}
config, err := client.SSO.CreateConfig(ctx, grantex.CreateSsoConfigParams{
    IssuerURL:    "https://accounts.google.com",
    ClientID:     "your-client-id",
    ClientSecret: "your-client-secret",
    RedirectURI:  "https://myapp.com/sso/callback",
})
```

### Get Config

```go theme={null}
config, err := client.SSO.GetConfig(ctx)
fmt.Printf("Issuer: %s\n", config.IssuerURL)
```

### Delete Config

```go theme={null}
err := client.SSO.DeleteConfig(ctx)
```

### Get Login URL (legacy)

```go theme={null}
result, err := client.SSO.GetLoginURL(ctx, "acme-corp")
// Redirect user to result.AuthorizeURL
```

### Handle Callback

```go theme={null}
result, err := client.SSO.HandleCallback(ctx, authCode, state)
fmt.Printf("User: %s (%s)\n", *result.Name, *result.Email)
```

## Legacy Types

### `SsoConfig`

| Field | Type | Description |
| - | - | - |
| `IssuerURL` | `string` | OIDC issuer URL |
| `ClientID` | `string` | OAuth client ID |
| `RedirectURI` | `string` | Callback URL |
| `CreatedAt` | `string` | ISO 8601 timestamp |
| `UpdatedAt` | `string` | ISO 8601 timestamp |

## Ownership

Grantex is owned by Orchestrum Technologies LLP. Inventor and owner: Sanjeev Kumar. Ownership contact: [sanjeev@orchestrum.in](mailto:sanjeev@orchestrum.in) or [mishra.sanjeev@gmail.com](mailto:mishra.sanjeev@gmail.com).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.