# Sign-in and OAuth

> How people sign in to nexxus, and the five things "OAuth" can mean here. Includes the full guide for sign-in with Microsoft Entra ID with the exact redirect URI, the redirect URI for connectors, AI clients such as Claude Code, nexxus as an identity provider for other applications, and what is not supported.

People sign in to nexxus with a password, with a one-time link by email, or with their Microsoft account. Which of these your team uses is up to you. If your company already runs Microsoft 365, the Microsoft sign-in saves everyone a separate password, and new colleagues get their account on their first click.

The word "OAuth" turns up in several places in nexxus, and each time it means something different. This page sorts them out, then walks through the Microsoft setup step by step.

## How people sign in

<Screenshot src={shot_signin_login_page} url="/login" alt={"The login page with the password form and the sign-in link"} caption={"The button Sign in with Microsoft appears once Microsoft sign-in is set up."} />

| Way | What the person does | Set up by |
|---|---|---|
| **Password** | Types username and password, optionally ticks <Ui>Stay signed in</Ui> | Nothing to set up. The password comes from the [invitation](/setup/invite-users/) |
| **Sign-in link by email** | Clicks <Ui>Email me a sign-in link</Ui>, enters their email address and opens the link | Works as soon as mail sending is set up |
| **Microsoft** | Clicks <Ui>Sign in with Microsoft</Ui> | An administrator, once, [as described below](#sign-in-with-microsoft-entra-id) |

All three lead to the same kind of session, with the same roles. If the account has an authenticator app, nexxus asks for its code after the password and after the sign-in link. Microsoft sign-ins leave multi-factor authentication to Microsoft. Details on codes, remembered browsers and sessions are in [Security](/setup/security/).

:::note[Username, not email]
The first field of the sign-in form is labelled <Ui>Email address</Ui>, but nexxus looks the account up by its **username**. For people invited in bulk and for accounts created by a Microsoft sign-in, the username is the email address, so both work. For everyone else, type the username.
:::

Scripts and tools sign in without a browser, with a personal API token. See [Security](/setup/security/#personal-api-tokens).

## Five things called OAuth

<Hub
  inputsLabel="Signing in to nexxus"
  inputs={[
    { icon: 'log-in', title: 'Sign in with Microsoft', text: 'Your Entra ID confirms who the person is' },
    { icon: 'bot', title: 'AI clients (MCP)', text: 'Claude Code and other MCP clients work as the person' },
  ]}
  core={{
    title: 'nexxus',
    text: 'One backend, one public address',
    items: ['/api/entra/code', '/api/plugins/oauth/callback', '/oauth2', '/api/mcp'],
  }}
  outputsLabel="nexxus signs in elsewhere"
  outputs={[
    { icon: 'plug-zap', title: 'Connector connections', text: 'The AI Assistant acts in Microsoft 365, Google, Slack with the person\'s account' },
    { icon: 'cable', title: 'MCP connectors with OAuth', text: 'External MCP servers that sign people in' },
    { icon: 'app-window', title: 'Other applications', text: 'Sign in with the nexxus account (API only)' },
  ]}
  caption="Each of the five has its own address. Only Microsoft sign-in and connector connections need an app registration that you create yourself."
/>

| Meaning | What it does | Where you set it up | Address you register |
|---|---|---|---|
| **Sign in with Microsoft** | People sign in to nexxus with their Microsoft account | <MenuPath to="Administration > Operations > Setup" />, step <Ui>Microsoft Entra</Ui>, or the system configuration | `https://<your nexxus address>/api/entra/code` |
| **Connector connections** | The AI Assistant works in another service with the person's own account | <MenuPath to="AI > Set up AI > Connectors" />, on the connector's page | `https://<your nexxus address>/api/plugins/oauth/callback` |
| **MCP connectors with OAuth** | The AI Assistant uses an external MCP server that signs people in itself | <MenuPath to="AI > Set up AI > Connectors" />, tab <Ui>My connectors</Ui> | Nothing: nexxus registers itself at the server |
| **nexxus as identity provider** | Another application lets people sign in with their nexxus account | API only, no page in the app | The other application's own redirect URI, entered in nexxus |
| **AI clients (MCP)** | Claude Code or another MCP client works in nexxus as the signed-in person | <MenuPath to="Administration > Operations > System configuration" />, then each person under <MenuPath to="My profile > AI clients" /> | Nothing: the client registers itself at nexxus |

`<your nexxus address>` is the public address of the nexxus backend. On most installations the app and the backend share one address, such as `https://nexxus.example.com`. The setup step and the connector page both show the exact value with a <Ui>Copy</Ui> button, so you never need to type it.

## Sign in with Microsoft (Entra ID)

With Microsoft sign-in, the login page shows a button <Ui>Sign in with Microsoft</Ui>. The person signs in at Microsoft as they do for Outlook or Teams, comes back to nexxus and is signed in. If nexxus does not know the person yet, it creates the account on the spot, with the default role from your setup.

<Flow
  steps={[
    { icon: 'mouse-pointer-click', title: 'Click', text: 'Sign in with Microsoft on the nexxus login page.' },
    { icon: 'log-in', title: 'Microsoft', text: 'The person signs in at Microsoft, with MFA if your directory requires it.', tone: 'cyan' },
    { icon: 'user-round-check', title: 'nexxus finds the account', text: 'By Microsoft identity, then by email address, or creates a new one.', tone: 'lavender' },
    { icon: 'circle-check-big', title: 'Signed in', text: 'With the roles held in nexxus. Microsoft only says who the person is.' },
  ]}
  caption="Rights stay in nexxus. The directory answers who someone is, never what they may do."
/>

### Before you start

- You hold the system role ADMIN in nexxus.
- Someone can register an application in your Microsoft Entra directory. Usually that is your IT. They need one value from you (the redirect URI) and give you three values back.
- You know the public address of nexxus. The setup step shows the redirect URI built from it.
- Your installation has one tenant. With more than one tenant, the login page does not offer Microsoft sign-in.

### Step 1: Register an app in Microsoft Entra

These steps happen in the Microsoft Entra admin center, whose menus are in English.

<Steps>

1. Open the Microsoft Entra admin center at `https://entra.microsoft.com`, go to **Entra ID** › **App registrations** › **New registration**. Give the app a name your people recognise, for example "nexxus". Under **Supported account types**, choose **Single tenant only**.

2. In the new app, open **Authentication** › **Add Redirect URI**, choose the platform **Web** and enter the redirect URI:

   ```text
   https://<your nexxus address>/api/entra/code
   ```

   It must match exactly, including `https` and without a slash at the end. Copy it from the nexxus setup step (Step 2) to be safe.

3. Open **API permissions**. nexxus needs the delegated Microsoft Graph permission **User.Read**, which reads the signed-in person's own profile. If it is not listed, add it under **Add a permission** › **Microsoft Graph** › **Delegated permissions**.

4. Open **Certificates & secrets** › **Client secrets** › **New client secret**. Copy the **Value** right away: Microsoft shows it only once. Note the expiry date. Secrets expire after 24 months at most, and sign-in stops working when the secret has expired.

5. On the **Overview** page, copy the **Application (client) ID** and the **Directory (tenant) ID**.

</Steps>

:::tip[One app for sign-in and the Microsoft connectors]
The Microsoft connectors (Outlook, Teams, SharePoint, OneDrive and others) can reuse this app. Add the connector redirect URI `https://<your nexxus address>/api/plugins/oauth/callback` as a second **Web** redirect URI to the same app, and leave the client ID empty when you set up the connector. See [Set up a connector](/plugins/set-up/).
:::

### Step 2: Enter the values in nexxus

<Screenshot src={shot_signin_entra_setup} url="/einrichtung" alt={"The setup step for sign-in with Microsoft, filled in with example values"} caption={"Filled in, before the test."} />

<Steps>

1. Open <MenuPath to="Administration > Operations > Setup" /> and choose the step <Ui>Microsoft Entra</Ui>. The page is titled <Ui>Sign in with Microsoft</Ui>.

2. In the card <Ui>App registration in Entra</Ui>, enter the three values from Microsoft:

   | Field in nexxus | Value from Microsoft |
   |---|---|
   | <Ui>Directory ID (tenant)</Ui> | Directory (tenant) ID |
   | <Ui>Application ID (client)</Ui> | Application (client) ID |
   | <Ui>Client secret</Ui> | The Value of the client secret |

   The secret is <Ui>Stored encrypted and never shown again.</Ui> Once saved, the field stays empty: leave it empty to keep the stored secret.

3. Check <Ui>Redirect URI, enter it in Entra</Ui>. nexxus fills it in from its own address. It must be identical to the redirect URI in the Microsoft app. Use <Ui>Copy</Ui> to pass it on.

4. Click <Ui>Test connection</Ui>. nexxus asks Microsoft whether the directory exists and whether the app with this secret gets a token. Three checks appear:

   | Check | Good result | If it fails |
   |---|---|---|
   | <Ui>Details</Ui> | | „Verzeichnis-ID, Anwendungs-ID und Geheimnis müssen gespeichert sein.“ (directory ID, application ID and secret must be saved) |
   | <Ui>Directory</Ui> | „Verzeichnis gefunden.“ (directory found) | „Microsoft kennt diese Verzeichnis-ID nicht.“ (Microsoft does not know this directory ID) |
   | <Ui>Application and secret</Ui> | „Anwendung und Geheimnis angenommen.“ (application and secret accepted) | Microsoft's own error description, for example for a wrong or expired secret |

5. Click <Ui>Save</Ui>. The message <Ui>Sign-in with Microsoft saved</Ui> confirms it. Saving here also switches the button on the login page on.

</Steps>

If you do not use Microsoft 365, choose <Ui>Not needed</Ui> under <Ui>Remind me later</Ui>. The step disappears, and invitations plus the sign-in link by email are all your team needs.

### Step 3: Fine-tune in the system configuration

The same settings, and two more, are in <MenuPath to="Administration > Operations > System configuration" />, section <Ui>Microsoft Entra ID sign-in</Ui>. Changes apply at once, without a deployment.

<Screenshot src={shot_signin_entra_sysconfig} url="/system-config" alt={"The Microsoft sign-in settings in the system configuration"} />

| Field | Meaning |
|---|---|
| <Ui>Show “Login via Microsoft”</Ui> | Switches the button on or off (<Ui>Shown</Ui> / <Ui>Hidden</Ui>). Off means the server hands out no sign-in address at all. |
| <Ui>Directory ID (tenant)</Ui>, <Ui>Application ID (client)</Ui>, <Ui>Client secret</Ui> | As in the setup step. <Ui>Remove saved secret</Ui> deletes the stored secret on the next save. |
| <Ui>Redirect URI (callback)</Ui> | <Ui>Must be registered exactly like this as a redirect URI in the customer’s app registration.</Ui> |
| <Ui>Target after sign-in</Ui> | Where the browser goes after a successful sign-in. Empty means the address of the app. |
| <Ui>Role for new accounts</Ui> | The system role that accounts created by a Microsoft sign-in receive. STAFF unless you change it. It must exist. |

If the button is switched on but the directory ID, application ID or redirect URI is missing, the page warns you and the button stays hidden until all three are set.

### Step 4: Try it

Sign out, or open a private browser window, and go to the login page. Below the password form you see <Ui>Sign in with Microsoft</Ui>. Sign in with a Microsoft account of your directory. You land in nexxus, and the person's tab <Ui>Activity</Ui> records the sign-in <Ui>via Microsoft</Ui>.

### Who gets an account

At every Microsoft sign-in, nexxus looks for the matching account in this order:

1. **By Microsoft identity.** An account that has signed in with Microsoft before is found by its Microsoft object id.
2. **By address.** Otherwise nexxus compares the Microsoft address (the `mail` field, or the user principal name) with the usernames and then with the email addresses of existing accounts. A person you invited before is linked to their Microsoft identity this way, without a second account.
3. **New account.** If nothing matches, nexxus creates an account: the username is the user principal name, the names come from Microsoft, it gets the system role from <Ui>Role for new accounts</Ui> and the default access role from the setup, tenant-wide. It has no usable password; the person always signs in with Microsoft.

Everyone in your directory who may sign in to this app gets an account at their first sign-in. To limit Microsoft sign-in to certain people, restrict who may use the app in Microsoft Entra.

The account remains an ordinary nexxus account. You assign roles in nexxus as usual (see [Roles and rights](/setup/roles/)). By default, Microsoft sign-ins are remembered on the browser for 30 days, so people do not sign in again every morning.

### Two-factor authentication with Microsoft

nexxus does not ask Microsoft users for its own authenticator code. Multi-factor authentication for these sign-ins is set up in Microsoft Entra, where your IT already manages it for Outlook and Teams.

One rule still applies: if a role is in the <Ui>Mandatory two-factor authentication</Ui> list and the account has no authenticator app yet, nexxus leads the person through the setup of one after the Microsoft sign-in. See [Security](/setup/security/#mandatory-two-factor-authentication-per-role).

### When Microsoft sign-in goes wrong

Errors on the way back from Microsoft are short English texts in the browser.

| What you see | Cause and fix |
|---|---|
| No <Ui>Sign in with Microsoft</Ui> button | The switch <Ui>Show “Login via Microsoft”</Ui> is off, or the directory ID, application ID or redirect URI is missing. On installations with more than one tenant the button is not offered. |
| Microsoft reports that the redirect URI does not match | The redirect URI in the Microsoft app differs from the one in nexxus. Copy it again from nexxus and enter it exactly. |
| `Token exchange with Microsoft failed` | The client secret is wrong or has expired. Create a new secret in Microsoft and enter it in nexxus. |
| `Could not load the Microsoft profile` | nexxus could not read the person's profile from Microsoft Graph. Check that the app has the delegated permission User.Read. |
| `Sign-in could not be mapped to an account` | The address belongs to a nexxus account that is already linked to a different Microsoft identity, or the role under <Ui>Role for new accounts</Ui> does not exist. |
| `Invalid state` | The sign-in was started in another browser, or the browser lost its session on the way, for example because cookies were cleared. Start again from the login page. |
| Cancelled at Microsoft | nexxus returns to the login page. Nothing is logged as an error. |

## Connector connections

A connector lets the AI Assistant work in another service, such as reading mail in Outlook or posting in Slack, with the account of the person who asks. The person connects their account once with OAuth; nexxus keeps the tokens encrypted and never hands them to the AI model.

For this you register an app at the provider once, with **one redirect URI for every connector and provider**:

```text
https://<your nexxus address>/api/plugins/oauth/callback
```

The connector page shows it under <Ui>Setup</Ui> as <Ui>Redirect URI (enter it in the provider's app)</Ui>, together with step-by-step instructions for the provider and the permissions the app must allow. Installing a connector and entering its client ID and secret needs the key `ai.plugin.admin`; connecting an own account needs <Ui>Connect own account</Ui> (`ai.plugin.connect`).

The full walk-through per connector, including the Microsoft, Google and Slack apps, is in [Set up a connector](/plugins/set-up/).

## MCP connectors with OAuth

Some MCP servers sign people in with OAuth themselves. When you add such a server under <Ui>My connectors</Ui> › <Ui>New</Ui> › <Ui>MCP connector</Ui>, choose <Ui>OAuth (dynamic registration)</Ui> under <Ui>Sign-in</Ui>. There is nothing to register by hand: at the first connection nexxus discovers the server's sign-in service, registers itself there as an application called "Nexxus" and leads the person through the server's own sign-in. The redirect URI is the same `/api/plugins/oauth/callback`.

## AI clients such as Claude Code

nexxus is an MCP server. Claude Code, claude.ai and other MCP clients connect to `https://<your nexxus address>/api/mcp`, sign the person in through OAuth and then work with that person's rights. An administrator allows this once per organisation; each person connects their own client. The whole guide is in [Connect Claude Code](/mcp/connect/).

## nexxus as an identity provider for other applications

nexxus can be the sign-in for other applications of yours, through standard OpenID Connect (authorization code flow, with PKCE for browser and mobile apps). A person who is already signed in to nexxus passes straight through, without typing a password again.

:::note[API only]
There is no page in the app for this. You register applications through the REST API, as an administrator.
:::

### Endpoints

| Endpoint | Purpose |
|---|---|
| `/.well-known/openid-configuration` | Discovery document. Point the other application here. |
| `/oauth2/authorize` | Starts the sign-in |
| `/oauth2/token` | Exchanges the code for tokens |
| `/oauth2/jwks` | Public keys to check the token signature (RS256) |
| `/userinfo` | Profile of the signed-in person |
| `/connect/logout` | Sign-out started by the other application |
| `/oauth2/consent` | The consent page, shown when the application asks for approval (`requireConsent`) |
| `/oauth2/register` | Self-registration (RFC 7591), for [AI clients](/mcp/connect/) only. Register your own applications through the API below. |

A person who is not signed in to nexxus yet is sent to the normal nexxus login page and comes back to the requesting application afterwards. The login works as it does everywhere else: with the code from the authenticator app, the sign-in link, or Microsoft.

The consent page comes from the server and is in German. It names the application, the account it signs in as, and what the application may see, for example „Ihre E-Mail-Adresse sehen“ (see your email address). <Ui>Verbinden</Ui> agrees, <Ui>Abbrechen</Ui> refuses.

### Register an application

Call the API as an administrator, for example with an administrator's [API token](/setup/security/#personal-api-tokens):

```bash
curl -X POST "https://<your nexxus address>/api/sso-client/" \
  -H "Authorization: Bearer cx_..." \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Wiki",
        "redirectUris": "https://wiki.example.com/oauth/callback",
        "scopes": "openid,profile,email"
      }'
```

The answer contains the generated `clientId` (starting with `sso_`) and, for confidential clients, the secret in `generatedClientSecret`. The secret is shown **only in this answer**; nexxus stores a hash. To get a new one later:

```bash
curl -X POST "https://<your nexxus address>/api/sso-client/action/regenerateSecret" \
  -H "Authorization: Bearer cx_..." -H "Content-Type: application/json" \
  -d '{"id": 3}'
```

| Field | Type | Required | Meaning |
|---|---|---|---|
| `name` | string | yes | Name of the application |
| `redirectUris` | string | yes, unless the client only uses client credentials | Allowed redirect URIs, one per line, matched exactly |
| `clientId` | string | no | Generated when empty (`sso_` plus 24 characters). Cannot be changed later. |
| `postLogoutRedirectUris` | string | no | Where the other application may send people after sign-out, one per line |
| `scopes` | string | no | Comma-separated. Default `openid,profile,email` |
| `publicClient` | boolean | no | `true` for browser and mobile apps: no secret, PKCE required. Default `false` |
| `requireConsent` | boolean | no | Asks the person to approve the application on first sign-in. Default `false` |
| `allowClientCredentials` | boolean | no | Also allows machine-to-machine tokens (confidential clients only). Default `false` |
| `enabled` | boolean | no | Default `true`. A disabled client cannot sign anyone in. |
| `accessTokenTtlSeconds` | number | no | Lifetime of access tokens. Default 900 (15 minutes) |
| `refreshTokenTtlSeconds` | number | no | Lifetime of refresh tokens. Default 2592000 (30 days). Each refresh issues a new refresh token. |
| `description` | string | no | Free text |

Confidential clients authenticate at the token endpoint with `client_secret_basic` or `client_secret_post`.

### What the tokens contain

The ID token carries these claims. `/userinfo` returns only the standard OpenID claims of the requested scopes (`sub`, email, names, `preferred_username`).

| Claim | Scope | Value |
|---|---|---|
| `sub` | always | The username. Once an account is deleted, it can go to a new account. |
| `user_id` | always | The person's id in nexxus. It is never given out again: link your application's accounts by it, not by `sub`. |
| `amr` | always, when known | How the session was signed in ([RFC 8176](https://www.rfc-editor.org/rfc/rfc8176)): `pwd` password, `otp` code from the authenticator app, `swk` a browser on which the code was typed within the last 30 days, `email` sign-in link, `fed` Microsoft, `mfa` two factors. Missing for a session restored by <Ui>Stay signed in</Ui> and while someone acts as another person. |
| `auth_time` | with `amr` | When the session was signed in, in seconds since 1970 |
| `preferred_username` | always | The nexxus username |
| `email`, `email_verified` | `email` | Email address, and whether it was confirmed |
| `name`, `given_name`, `family_name` | `profile` | Names |
| `roles` | `profile` | The person's system roles |
| `tenant_id` | `profile` | The person's tenant |

## What is not supported

- **Other identity providers for the nexxus login.** Google, Okta, Keycloak or any other OpenID Connect provider cannot be used to sign in to nexxus. Microsoft Entra ID is the only external sign-in.
- **SAML** is not supported, in either direction.
- **SCIM** or any other automatic provisioning from a directory. Accounts come from invitations, from direct creation or from a first Microsoft sign-in.
- **Self-registration.** <Ui>No account yet?</Ui> <Ui>Contact your administrator</Ui> on the login page does not open a sign-up form. An administrator creates every account.
- **Registering applications for nexxus as identity provider in the app.** This is API only, as described above.

## What happens behind the scenes

<Sequence
  actors={[
    { name: 'Browser', icon: 'monitor' },
    { name: 'nexxus', icon: 'server' },
    { name: 'Microsoft Entra', icon: 'key-round' },
    { name: 'Microsoft Graph', icon: 'contact' },
  ]}
  steps={[
    { from: 0, to: 1, label: 'Click "Sign in with Microsoft"' },
    { from: 0, to: 2, label: 'Go to the Microsoft sign-in, with a PKCE challenge' },
    { from: 2, to: 2, label: 'The person signs in, with MFA if required' },
    { from: 2, to: 1, label: 'Back to /api/entra/code with a one-time code' },
    { from: 1, to: 2, label: 'Exchange the code, with client secret and PKCE verifier' },
    { from: 1, to: 3, label: "Read the person's profile" },
    { from: 1, to: 1, label: 'Find or create the account' },
    { from: 1, to: 0, label: 'Signed in to nexxus' },
  ]}
  caption="The code exchange runs between servers. The browser never sees the client secret, and nexxus keeps no Microsoft tokens."
/>

- **Microsoft sign-in** uses the authorization code flow with PKCE. nexxus exchanges the code on the server with the client secret, then reads the person's profile from Microsoft Graph. It does not keep Microsoft's tokens.
- **The client secret** of the Entra app is stored encrypted with the installation's secrets key, and decrypted only for the exchange.
- **The login button** appears only when the server hands the login page a Microsoft sign-in address. It does so when the switch is on and the directory ID, application ID and redirect URI are set.
- **The identity provider** signs its tokens with an RSA key (RS256). The signed-in nexxus session is the single sign-on session: a person signed in to the nexxus app passes straight through, and a person who signs in on the way to another application is signed in to the nexxus app as well.
- **Authorizations** (the record that a person approved an application, and its refresh tokens) are kept in the database. nexxus stores every token value only as a SHA-256 hash, so a copy of the database contains no usable token.

:::note[For self-hosters]
| Setting | Purpose |
|---|---|
| `NEXXUS_SSO_ISSUER` | The public HTTPS address of the backend. It is the `iss` of the identity provider's tokens, the base of the connector redirect URI and the base of the MCP address (`/api/mcp`). Set it on every deployed installation; when empty, nexxus derives it from the incoming request. |
| `NEXXUS_SSO_RSA_PRIVATE_KEY` | RSA signing key for the identity provider, PKCS#8 PEM (`openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048`). When empty, nexxus generates a new key at every start, and all issued tokens become invalid on restart. |
| `NEXXUS_SECRETS_KEY` | Encrypts the Entra client secret, connector secrets and other stored credentials (`openssl rand -base64 32`). |
| `APP_FRONTEND_BASEURL` | Where the browser goes after a Microsoft sign-in when <Ui>Target after sign-in</Ui> is empty. |
| `app.cors.allowedOrigins` | Browser applications that call `/oauth2/token` directly need their origin listed here. |
:::

## Next steps

<CardGrid>
  <LinkCard title="Security" description="Two-factor authentication, sessions, API tokens and the audit log." href="/setup/security/" />
  <LinkCard title="Set up a connector" description="Register the provider app and connect accounts." href="/plugins/set-up/" />
  <LinkCard title="Invite users" description="Accounts for everyone who does not sign in with Microsoft." href="/setup/invite-users/" />
</CardGrid>