# OAuth integration with Subrite OIDC

**Summary:** Let users sign in to your application with their Subrite account through the OIDC web application flow with PKCE: redirect to the login page, exchange the code for tokens, refresh tokens, log out, and call the member API.

- Space: [Developers](https://www.subrite.no/developers)
- Source: https://www.subrite.no/developers/oauth-integration
- Updated: 2026-09-23
- Markdown index: https://www.subrite.no/developers/llms.txt

Subrite provides the Web Application Flow to authorize users with standard OAuth that runs in the browser.

To enable OAuth login in your application, you need to register your application in the Subrite Admin Portal. You will receive a **client ID**, **client secret**, and **redirect URI** for your application. Once you have the required parameters, follow the steps below to enable OAuth login.

Subrite OIDC follows the **PKCE standard**, so you need to add **code\_challenge\_method** and **code\_challenge** when you make the very first request to authenticate. If you use a standard library to generate the code, you also need to keep the **code\_verifier** so you can use it later to get access tokens.

<a id="required-parameters"></a>
## Required parameters

- **Client ID** (`client_id`)
- **Client secret** (`client_secret`)
- **Redirect URI** (`redirect_uri`)

<a id="oidc-client-configuration-options"></a>
## OIDC client configuration options

When registering your OIDC client in the Subrite Admin Portal, you can configure the following options.

<a id="disable-signup"></a>
### Disable signup

The **Disable signup** option prevents new user registration through the OIDC authentication flow. When it is enabled:

- Users are always directed to the **sign-in page** (never sign-up).
- The sign-up link is **hidden** on the sign-in page.
- Only users with **existing accounts** can authenticate.

This is useful for:

- **Apple Reader App** compliance (Apple requires that reader apps do not allow new user creation through authentication).
- Applications that require users to have pre-existing accounts.
- Scenarios where user registration should be handled through a separate process.

> [!NOTE]
> When **Disable signup** is enabled, any `{"action":"signup"}` in the state parameter is ignored, and users are always directed to sign in.

<a id="flow-steps"></a>
## Flow steps

1. Users are redirected to the Subrite OIDC login page.
2. Users are authenticated.
3. Users are redirected to the registered redirect URI.

<a id="environment-urls"></a>
## Environment URLs

- **Stage URL:** will be provided once it is prepared.
- **Production URL:** will be provided once it is prepared.

<a id="step-1-redirect-to-the-subrite-login-page"></a>
## Step 1: Redirect to the Subrite login page

<a id="endpoint"></a>
### Endpoint

**GET** `/api/oidc/auth`

| Parameter | Required | Type | Description |
| --- | --- | --- | --- |
| `response_type` | Yes | string | Grant to execute. Only `code` is currently supported. |
| `client_id` | Yes | string | Your Client ID. |
| `redirect_uri` | Yes | string | A successful response from this endpoint results in a redirect to this URL. Must include all registered redirect URIs. |
| `code_challenge_method` | Yes | `S256` | Required because PKCE is enabled. |
| `code_challenge` | Yes | string | Base64 encoded SHA256 hash of a long character string. |
| `scope` | Yes | string | `openid`, `offline_access` |
| `state` | No | string | An opaque value, used for security purposes. Can encode JSON with `action: "signup"` to direct users to the registration page. If this parameter is set in the request, it is returned to the application as part of the `redirect_uri`. |

<a id="note-about-redirect-uri"></a>
### Note about `redirect_uri`

If a tenant assigns multiple `redirect_uri`s to a single client ID in Subrite, **every request must include a** **`redirect_uri`**. If multiple `redirect_uri`s are registered in Subrite and a request **lacks a** **`redirect_uri`**, an exception is thrown.

**Example:**

```bash
redirect_uri=http%3A%2F%2Flocalhost%3A3010%2Fcallback,http%3A%2F%2Flocalhost%3A3010%2Falt-callback
```

<a id="direct-users-to-signup"></a>
### Direct users to signup

To direct users to the registration (signup) page instead of the login page, encode the action in the `state` parameter.

**Example use case:** if your application has a "Subscribe" or "Sign Up" button, encode `{"action":"signup"}` in the state parameter:

```bash
# state={"action":"signup"} URL encoded to %7B%22action%22%3A%22signup%22%7D
http://{{subriteUrl}}/api/oidc/auth?client_id=your-client-id&response_type=code&scope=openid+offline_access&redirect_uri=http%3A%2F%2Flocalhost%3A3010%2Fcallback&code_challenge=abc&code_challenge_method=S256&state=%7B%22action%22%3A%22signup%22%7D
```

**You can combine the signup action with CSRF protection:**

```json
{
  "action": "signup",
  "csrf": "your-csrf-token"
}
```

<a id="example-request-urls"></a>
### Example request URLs

**For the login flow:**

```bash
http://{{subriteUrl}}/api/oidc/auth?client_id=your-client-id&response_type=code&scope=openid+offline_access&redirect_uri=http%3A%2F%2Flocalhost%3A3010%2Fcallback&code_challenge=abc&code_challenge_method=S256&state=csrf-token
```

**For the signup flow:**

```bash
# state={"action":"signup"} URL encoded
http://{{subriteUrl}}/api/oidc/auth?client_id=your-client-id&response_type=code&scope=openid+offline_access&redirect_uri=http%3A%2F%2Flocalhost%3A3010%2Fcallback&code_challenge=abc&code_challenge_method=S256&state=%7B%22action%22%3A%22signup%22%7D
```

<a id="step-2-the-user-authenticates"></a>
## Step 2: The user authenticates

Once the user completes the request in Step 1, they are redirected to the **Subrite OAuth login page** and asked to authenticate.

<a id="authentication-process"></a>
### Authentication process

- The user enters their credentials and **logs in**.
- After successful authentication, the user is redirected to the **registered** **`redirect_uri`**.
- The response contains:
  - A **temporary authorization code** (`code`).
  - The **state** parameter (if provided in the initial request).

> [!WARNING]
> **Important**
>
> - The **authorization code is valid for only 10 minutes**.
> - This code must be exchanged for an **access token** in the next step.

<a id="step-3-exchange-the-code-for-a-token"></a>
## Step 3: Exchange the code for a token

When the user comes back to your `redirect_uri`, read the `code` from the request URL. Then make a **POST** request to the token endpoint with the following parameters:

- `code`
- `code_verifier`
- `grant_type`
- `client_id`
- `client_secret`

You get the `code_verifier` when you generate the `code_challenge` in Step 1. The token endpoint responds with an **access token** and a **refresh token**.

<a id="token-endpoint"></a>
### Token endpoint

```text
POST /api/oidc/token
```

<a id="headers"></a>
### Headers

```text
Content-Type: application/x-www-form-urlencoded
Accept: application/json
```

<a id="required-parameters-2"></a>
### Required parameters

| Parameter | Required | Type | Description |
| --- | --- | --- | --- |
| `grant_type` | Yes | string | `authorization_code` |
| `client_id` | Yes | string | Your Client ID. |
| `client_secret` | Yes | string | Your Client Secret. |
| `code` | Yes | string | The code you received in the `code` parameter in Step 2. |
| `code_verifier` | Yes | string | The code verifier stored in the session when you generated `code_challenge` in Step 1. |

<a id="response"></a>
### Response

```text
{
  "token_type": "<string>",
  "expires_in": <integer>,
  "access_token": "<string>",
  "refresh_token": "<string>",
  "id_token": "<string>",
  "scope": "<string>"
}
```

<a id="use-the-access-token-to-call-the-subrite-api"></a>
## Use the access token to call the Subrite API

Once you have the **access token**, use it in the `Authorization` header as a **Bearer token** to access resources from the Subrite API.

<a id="example-request"></a>
### Example request

```sh
curl --location 'https://stage.api.subrite.no/api/v1/members/profile/info-with-active-subscriptions' \
--header 'Authorization: Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE3MDQ4NTk4NjIsInN1YiI6NSwiaXNzIjoiaHR0cHM6Ly9taW5zaWRl'
```

<a id="api-base-urls"></a>
### API base URLs

- **Stage:** [https://stage.api.subrite.no](https://stage.api.subrite.no)
- **Production:** [https://api.subrite.no](https://api.subrite.no)

<a id="response-sample"></a>
### Response sample

> [!NOTE]
> `subscriptions` only returns active subscriptions. If `subscriptions.length === 0`, the user does not have active subscriptions.

```javascript
{
  "id": "string", // Updated: memberId is now a string,Ex: "1", "c29a724f-36cf-4584-9d47-1cdde8733f75" etc
  "fullName": "string",
  "email": "string",
  "userType": "system_admin",
  "userId": 1234,
  "phone": "+4793155389",
  "avatar": null,
  "nickName": "",
  "memberNumber": 1234, // memberNumber is a number
  "address": {
    "line1": null,
    "city": null,
    "postCode": null,
    "postOffice": null,
    "country": "NO",
    "name": null,
    "email": null
  },
  "subscriptions": [
    {
      "subscriptionId": 1,
      "packageId": 2,
      "packageName": "example package",
      "status": "active",
      "createdAt": "2024-01-08T13:49:59.350Z",
      "activatedAt": "2024-01-08T13:49:59.350Z",
      "expiresAt": "2024-01-08T13:49:59.350Z",
      "subscriptionProducts": [
        {
          "id": 1,
          "name": "example product",
          "hasAccess": true
        }
      ],
      "customPropertyValues": {
        "muscNumber": 1234,
        "localClub": "",
        "mufcNumber": 1234568,
        "mufcExpiration": "2023-12-27T18:00:00.000Z"
      }
    }
  ]
}
```

<a id="refresh-the-access-token"></a>
## Refresh the access token

To get a new access token with the refresh token, use the **same token endpoint** with `grant_type` set to `refresh_token`.

<a id="token-endpoint-2"></a>
### Token endpoint

```text
POST /api/oidc/token
```

<a id="headers-2"></a>
### Headers

```text
Content-Type: application/x-www-form-urlencoded
Accept: application/json
```

<a id="required-parameters-3"></a>
### Required parameters

| Parameter | Required | Type | Description |
| --- | --- | --- | --- |
| `grant_type` | Yes | string | `refresh_token` |
| `client_id` | Yes | string | Your Client ID. |
| `client_secret` | Yes | string | Your Client Secret. |
| `refresh_token` | Yes | string | The refresh token you received in Step 3. |

<a id="example-request-2"></a>
### Example request

![Postman POST request to {{subriteUrl}}/api/oidc/token with a form-urlencoded body containing client\_id, client\_secret, grant\_type set to refresh\_token, and refresh\_token, and the JSON response with access\_token, expires\_in, id\_token, refresh\_token, scope and token\_type](https://cdn.sanity.io/images/1x2xswq6/production/36d1f1e62e535e09df6e6d2766ebda5e8cd54541-1082x570.png?w=1600&fit=max&auto=format)

*A refresh token request to the token endpoint and its response.*

<a id="logout"></a>
## Logout

When you register your application in the **Subrite Admin Portal**, you also provide a `post_logout_redirect_uri`. When implementing logout in your application, you must make sure the user is **logged out from both Subrite and your application**.

<a id="logout-flow"></a>
### Logout flow

1. **Log out from Subrite first.**
   - The browser must visit the **Subrite logout endpoint**.
   - The Subrite logout endpoint then redirects back to your application's configured `post_logout_redirect_uri`.
2. **Log out from your application.**

<a id="subrite-logout-endpoint"></a>
### Subrite logout endpoint

To log out from Subrite, make a **GET** request to:

```text
GET /api/oidc/session/end
```

You can also specify which client you are signing out from:

```text
GET /api/oidc/session/end?client_id=your_client_id
```

<a id="post-logout-redirection"></a>
### Post logout redirection

After logging out from Subrite, the user is redirected to the configured `post_logout_redirect_uri`.

<a id="php-sample-code"></a>
## PHP sample code

> [!NOTE]
> We strongly recommend using a **popular OpenID Connect library** for your programming language of choice. The example code below is written in **vanilla PHP** (without any library), but you can achieve the same functionality with any OpenID Connect library.

<a id="send-the-login-request-to-subrite-oidc"></a>
### Send the login request to Subrite OIDC

```text
 public function login(Request $request, $isSignup = false)
    {

        $codeVerifier = bin2hex(random_bytes(64));
        $codeChallenge = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '=');

        // Encode signup intent in state parameter
        if ($isSignup) {
            $state = json_encode(['action' => 'signup']);
        } else {
            $state = \bin2hex(\random_bytes(16)); // Regular CSRF token
        }

        session()->put('openid_connect_code_verifier', $codeVerifier);

        $authorizeUrl = 'http://localhost:3000/api/oidc/auth' ;
        $clientId = 'example-client-id';
        $redirectUri = 'http://localhost:3010/callback';

        $query = [
            'client_id' => $clientId,
            'response_type' => 'code',
            'scope' => 'openid offline_access',
            'redirect_uri' => $redirectUri,
            'code_challenge' => $codeChallenge,
            'code_challenge_method' => 'S256', // required as have PKCE support enabled
            'state' => $state,
        ];

        $url = $authorizeUrl . '?' . http_build_query($query);

        return redirect()->away($url);
    }
```

*PHP*

<a id="request-the-access-token"></a>
### Request the access token

After receiving the authorization `code` in the callback, make a request to the **token endpoint** to exchange it for an **access token**.

```text
public function callback(Request $request) {
        $tokenEndpoint = 'http://localhost:3000/api/oidc/token';
        $code = $request->get('code');
        $codeVerifier = session()->get('openid_connect_code_verifier');

        $response = Http::asForm()->post($tokenEndpoint, [
            'code' => $code,
            'grant_type' => 'authorization_code',
            'client_id' => 'example-client-id',
            'client_secret' => 'example-client-secret',
            'code_verifier' => $codeVerifier,
        ]);

        session()->forget('openid_connect_code_verifier');

        return $response->json();
    }
```

*PHP*

<a id="member-information-and-access-apis"></a>
## Member information and access APIs

<a id="get-logged-in-member-info-with-content-access"></a>
### Get logged-in member info with content access

Retrieve detailed information about the **logged-in member**, including their **content access rights**.

More details are in the API reference: [Get logged-in member info with content access](https://docs.subrite.no/api-reference#tag/member-profile/get/api/v1/members/profile/info-with-content-access)

<a id="get-logged-in-member-info-with-active-subscriptions"></a>
### Get logged-in member info with active subscriptions

Retrieve detailed information about the **logged-in member**, including their **active subscriptions**.

More details are in the API reference: [Get logged-in member info with active subscriptions](https://docs.subrite.no/api-reference#tag/member-profile/get/api/v1/members/profile/info-with-active-subscriptions)

<a id="checkout-search-parameters"></a>
## Checkout search parameters

You can add these search parameters when you send a user to the checkout page.

| Parameter | Description |
| --- | --- |
| `reference` | Indicates the source from which the user is navigating to the checkout page. It helps track the origin of the user within the application. Example: an article slug or URL. |
| `onCheckoutCompletedUrl` | The URL the user is redirected to after successfully completing the checkout process. This ensures they return to their original site or workflow and are automatically logged in. |
| `callBackUrl` | The URL the user is redirected to if they cancel the checkout process. It takes the user back to the original page or site they came from, so they can resume their previous activity. |

<a id="changelog"></a>
## Changelog

<a id="get-api-v1-members-profile-info-with-active-subscriptions"></a>
### `GET /api/v1/members/profile/info-with-active-subscriptions`

**Response update:**

- Removed `countryCode`.
- `phone` is now returned with the **country calling code**, for example:

```json
{
  "phone": "+4793155389"
}
```
