> ## 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 |
