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

# Enterprise sign-in

> Let a customer's staff sign in through their company's own identity provider.

A customer may want their staff to sign in to your app through the company's own identity
provider. There are two routes.

| Route | SAML | OpenID Connect |
| - | - | - |
| Your own connected sign-in service runs it | Yes, where that service offers it | Yes, where that service offers it |
| Generic OpenID Connect through Pramaan (below) | No | Yes |

An app whose sign-in is the backend Pramaan provisions (`auth.backend.*`) has **no SAML**. It
can use the generic OpenID Connect route.

## Through your own sign-in service

Set the enterprise connection up in your own sign-in service, then check each session with
`auth.token.verify` as you already do. Pramaan checks the session token. Your sign-in service
checked the enterprise identity. Membership, roles and linking accounts by email stay your
app's decisions, and an email domain alone grants nothing.

## Generic OpenID Connect

Pramaan checks the identity; your app creates the user and the session.

1. **Save the connection** once per customer with `auth.enterprise-connection.create`: your
   label for the customer, the issuer address, the client id and secret their administrator
   gave you, and your app's exact redirect addresses. It needs approval. The secret is
   write-only, and no result carries the issuer or the client id. Who among your customers may
   set a connection up is your app's own check.
2. **Start sign-in** with `auth.enterprise.start`. Your server makes a fresh `state`, `nonce`
   and PKCE code verifier, keeps them in the visitor's session, and sends `state`, `nonce` and
   the S256 `codeChallenge`. Redirect the visitor to the `authorizationUrl` you get back.
3. **When the visitor returns, compare `state` with the session's.** Stop if it differs.
4. **Redeem the code** with `auth.social.exchange`: `providerKey: "enterprise"`, the
   `connectionRef`, `code`, the same `redirectUri`, the `state` as it came back to your
   callback, `pkceVerifier` and `expectedNonce`. Only a sign-in you started is redeemed, once,
   within 10 minutes. Pramaan refuses a `state` that is not the one the sign-in was started
   with, so a mismatch is caught even if step 3 is forgotten; a refused `state` uses nothing
   up. Still make the comparison in step 3: only your app sees the visitor's session.

```json theme={null}
{
  "providerKey": "enterprise",
  "subjectId": "…",
  "email": "alex@example.com",
  "emailVerified": false,
  "displayName": "Alex Example"
}
```

## Rules for your app

* An identity is `connectionRef` plus `subjectId`, never `subjectId` alone. Keep the `ref` that
  create returned and key identities by your own id for that connection: a list row's `ref` is
  another string for the same connection.
* `emailVerified` is exactly what the identity provider stated. When it is `false`, do not link
  the sign-in to an existing account by that email and do not grant access because of it.
* A verified email is still only what one customer's identity provider says, and that provider
  can state any address. Never use the email to find or link an account from another connection
  or another way of signing in: find the account by `connectionRef` plus `subjectId` only.
* The issuer must be an `https` address on a public host name, and its discovery document must
  name it exactly. The addresses that document names must be on the issuer's own host, or on a
  host you listed in `endpointHosts` when you created the connection.
* `state` is for enterprise sign-in only: required with `connectionRef`, and refused as an
  unknown field without it.
* Redirect addresses are matched exactly. A code is used once: after an error, start again.


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