# Add sign-in to an app

Sign a person in to your application with OpenID Connect, using the authorization code flow with PKCE, and read who they are.

In this tutorial you sign a person in to an application and read their identity. You use the OpenID Connect authorization code flow with PKCE (RFC 7636), which is the flow every OpenID Connect library implements.

The steps show each request on the wire, so you can see what your library does for you. It takes about ten minutes.

## Before you begin

You need:

- An AuthFI project and its issuer, the project's URL. This tutorial uses `https://acme.authfi.io`.
- Permission to add applications in the AuthFI console.
- `curl` and `openssl`, or Node.js 18 or later, or Python 3.

<Steps>

### Register your application

1. In the console, open **Integrations > Applications** and select **Register**.
2. Under **What are you connecting?**, choose **OIDC / OAuth 2.0**.
3. Choose the kind that matches where your code runs:
   - **Web application** runs on your server and can keep a secret.
   - **Single-page app** runs in the browser and proves itself with PKCE instead of a secret.
4. Enter a **Name**. Under **Redirect URIs**, enter the URL in your application that AuthFI sends the person back to after they sign in. This tutorial uses `https://app.acme.com/callback`. Enter one URI per line.
5. Review the summary and select **Register**.

The last step shows the application's **Client id**. A web application also gets a **Client secret**, shown once: copy it, then tick **I have copied the client secret** to continue. The same step lists the project's endpoints, under **Paste these into the other side**.

```bash
export CLIENT_ID=<your client ID>
```

### Create a PKCE verifier and challenge

PKCE ties the sign-in to the application that started it. You create a random secret, the _verifier_, and send only its SHA-256 hash, the _challenge_. Later you prove you hold the verifier.

<Tabs labels={["Shell", "Node.js", "Python"]}>

```bash
VERIFIER=$(openssl rand -base64 48 | tr -d '=+/' | cut -c1-64)
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
STATE=$(openssl rand -hex 16)
```

```js
import { createHash, randomBytes } from "node:crypto";

const verifier = randomBytes(48).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");
const state = randomBytes(16).toString("hex");
```

```python
import base64, hashlib, os, secrets

verifier = base64.urlsafe_b64encode(os.urandom(48)).rstrip(b"=").decode()
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
state = secrets.token_hex(16)
```

</Tabs>

Keep the verifier and `state` in the person's session on your server. You need both when they come back.

### Send the person to sign in

Redirect the browser to the project's authorization endpoint.

<Endpoint method="GET" path="/oauth/authorize" />

<Tabs labels={["Shell", "Node.js", "Python"]}>

```bash
echo "https://acme.authfi.io/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.acme.com%2Fcallback&scope=openid%20profile%20email&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"
```

```js
const url = new URL("https://acme.authfi.io/oauth/authorize");
url.search = new URLSearchParams({
  response_type: "code",
  client_id: process.env.CLIENT_ID,
  redirect_uri: "https://app.acme.com/callback",
  scope: "openid profile email",
  state,
  code_challenge: challenge,
  code_challenge_method: "S256",
}).toString();

// Redirect the browser to `url`.
```

```python
from urllib.parse import urlencode

url = "https://acme.authfi.io/oauth/authorize?" + urlencode({
    "response_type": "code",
    "client_id": os.environ["CLIENT_ID"],
    "redirect_uri": "https://app.acme.com/callback",
    "scope": "openid profile email",
    "state": state,
    "code_challenge": challenge,
    "code_challenge_method": "S256",
})

# Redirect the browser to `url`.
```

</Tabs>

| Parameter               | Value in this tutorial          | Purpose                                                    |
| ----------------------- | ------------------------------- | ---------------------------------------------------------- |
| `response_type`         | `code`                          | Ask for an authorization code                              |
| `client_id`             | Your client ID                  | Identifies your application                                |
| `redirect_uri`          | `https://app.acme.com/callback` | Must match a registered redirect URI exactly               |
| `scope`                 | `openid profile email`          | `openid` is required. The others add profile and email.    |
| `state`                 | A random value                  | Returned unchanged, so you can match the answer to the ask |
| `code_challenge`        | The challenge                   | The PKCE challenge from the previous step                  |
| `code_challenge_method` | `S256`                          | The only method accepted                                   |

AuthFI shows your project's sign-in page. To try the flow by hand, open the URL in a browser and sign in.

### Receive the authorization code

After the person signs in, the browser returns to your redirect URI:

```text
https://app.acme.com/callback?code=Xq3v...&state=5f2a...&iss=https%3A%2F%2Facme.authfi.io
```

Compare `state` with the value you stored. If they differ, stop: the response does not belong to a sign-in you started.

The code works once and expires after 5 minutes.

```bash
export CODE=<the code from the URL>
```

### Exchange the code for tokens

Send the code and the verifier to the token endpoint. The body is form-encoded.

<Endpoint method="POST" path="/oauth/token" />

```bash
curl https://acme.authfi.io/oauth/token \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://app.acme.com/callback \
  -d client_id="$CLIENT_ID" \
  -d code_verifier="$VERIFIER"
```

A web application also sends its client secret, as HTTP Basic authentication or as `client_secret` in the body.

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "...",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ii4uLiJ9...",
  "scope": "openid profile email"
}
```

| Token           | Use it to                              | Lifetime              |
| --------------- | -------------------------------------- | --------------------- |
| `id_token`      | Learn who signed in                    | 15 minutes by default |
| `access_token`  | Call APIs on the person's behalf       | 15 minutes by default |
| `refresh_token` | Get new tokens without another sign-in | 30 days by default    |

### Validate the ID token

The ID token is a signed JWT. Before you trust it, validate it. An OpenID Connect library does this when you configure it with these values:

| Setting   | Value                                          |
| --------- | ---------------------------------------------- |
| Issuer    | `https://acme.authfi.io`                       |
| Keys      | `https://acme.authfi.io/.well-known/jwks.json` |
| Audience  | Your client ID                                 |
| Algorithm | `RS256`                                        |

The library checks the signature against the project's keys, and checks that `iss` is your issuer, `aud` is your client ID and `exp` is in the future.

The validated token tells you who signed in:

```json
{
  "iss": "https://acme.authfi.io",
  "sub": "ada@acme.com",
  "aud": "<your client ID>",
  "email": "ada@acme.com",
  "email_verified": true,
  "name": "Ada Lovelace",
  "groups": ["engineering"],
  "roles": ["developer"],
  "auth_time": 1760000000,
  "iat": 1760000000,
  "exp": 1760000900
}
```

</Steps>

## Check that it works

Call the userinfo endpoint with the access token.

```bash
curl https://acme.authfi.io/oauth/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```json
{
  "sub": "ada@acme.com",
  "email": "ada@acme.com",
  "email_verified": true,
  "name": "Ada Lovelace",
  "groups": ["engineering"],
  "roles": ["developer"]
}
```

If you see the person you signed in as, the flow works end to end.

## Troubleshooting

| What you see                                                     | Cause                                                              | Fix                                                                                                |
| ---------------------------------------------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| A `400` error with `invalid_request` instead of the sign-in page | The `redirect_uri` is not registered for the application           | Register the exact URI, including path and port                                                    |
| A `401` error with `invalid_client`                              | The `client_id` is not an application in this project              | Check the client ID and the issuer                                                                 |
| The browser returns with `error=access_denied`                   | The application is assigned to groups, and the person is in none   | On the application's page, select **Assign a group** and choose one of their groups                |
| `invalid_grant` from the token endpoint                          | The code expired, was already used, or the verifier does not match | Start again from _Send the person to sign in_. Exchange each code once, within 5 minutes.          |
| `invalid_client` from the token endpoint                         | The client secret is missing or wrong                              | A web application must send its client secret. If it is lost, rotate it on the application's page. |

Every error is listed in the [endpoint reference](/reference/oauth#errors).

## Next steps

<Cards>
  <Card href="/applications/sessions" title="Sessions, sign-out and MFA">
    Keep a person signed in with refresh tokens, sign them out, and require multi-factor authentication.
  </Card>
  <Card href="/applications/types" title="Application types and redirect URIs">
    Choose the right type, and see how redirect URIs are matched.
  </Card>
  <Card href="/applications/machine-to-machine" title="Service to service">
    Get a token for a backend service that has no person to sign in.
  </Card>
  <Card href="/reference/oauth" title="Endpoint reference">
    Every parameter and response of every OAuth and OpenID Connect endpoint.
  </Card>
</Cards>
