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

# OAuth client libraries (AUTH V2)

> What to configure in a stock OAuth 2.0 library to talk to AUTH V2, and the three defaults that need changing.

**AUTH V2 is standards-compliant OAuth 2.0, so a stock library works against it.** There is no Simkl-specific SDK to install and no vendor quirk to patch around — configure the endpoints, set PKCE to `S256`, and the library does the rest.

<Note>
  **Verification status, 2026-09-18.** All three snippets were run against production, not just read off each library's docs:

  | Library                    | Status                                                                                                                                                                                                                                       |
  | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | **openid-client** (Node)   | **Run against production.** The snippet configures itself from our discovery document and builds a correct authorize URL — right endpoints, `response_type=code`, `S256`, a valid 43-character verifier.                                     |
  | **Authlib** (Python)       | **Run against production.** Same: fetches and parses the document, resolves both endpoints, and builds an authorize URL carrying `S256`.                                                                                                     |
  | **Spring Security** (Java) | **Run against production.** `ClientRegistrations.fromIssuerLocation("https://simkl.com")` resolves, and hands back the right authorize and token URLs from one setting. Executed on Spring Security 5.8; discovery is the same class in 6.x. |

  What is *not* covered for any of them is the browser half — consent and the code exchange — since that needs a human. Those requests are verified separately against production in [the raw HTTP walkthrough](/api-reference/oauth2-authorization-code), so every leg of the flow is tested; just not end to end inside one library.
</Note>

## What every library needs

Whatever you are using, these are the settings. Everything else is that library's own naming.

| Setting                       | Value                                                                                                                  |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Authorization endpoint        | `https://simkl.com/oauth2/authorize`                                                                                   |
| Token endpoint                | `https://api.simkl.com/oauth2/token`                                                                                   |
| Revocation endpoint           | `https://api.simkl.com/oauth2/revoke`                                                                                  |
| Device authorization endpoint | `https://api.simkl.com/oauth2/device`                                                                                  |
| Issuer                        | `https://simkl.com`                                                                                                    |
| Grant type                    | `authorization_code`, plus `refresh_token`                                                                             |
| Scope                         | `media:read media:write` — space-separated. Some libraries take a list or a comma-separated string and join it for you |
| PKCE                          | **`S256`, mandatory.** Not optional, and `plain` is rejected                                                           |
| Client auth method            | `none` if your app has no secret; `client_secret_basic` or `client_secret_post` for **Server apps & services**         |

<Note>
  **Two hosts.** The consent page is on `simkl.com`; everything else is on `api.simkl.com`. A library that takes a single "base URL" will get one of them wrong — look for separate authorize and token settings.
</Note>

## The three defaults that need changing

Most libraries work as-is. These three do not, and each fails in a way that does not obviously point at the cause.

| Library                    | Default                                      | What happens                                                                  | Fix                                                    |
| -------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------ |
| **openid-client** (Node)   | Client auth defaults to `client_secret_post` | An app with no secret sends an empty one                                      | Pass `client.None()` as the fourth argument            |
| **openid-client** (Node)   | Discovery defaults to `algorithm: "oidc"`    | Fetches the OpenID Connect path, gets `404`, and stops — there is no fallback | Pass `{ algorithm: "oauth2" }`                         |
| **Spring Security** (Java) | PKCE only for clients without a secret       | A server app omits `code_verifier` and gets `400 invalid_request`             | Add `OAuth2AuthorizationRequestCustomizers.withPkce()` |

<a id="node-openid-client" />

## Node.js — openid-client v6

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
import * as client from "openid-client";

// Apps with no secret: client.None() is required. Without it the library
// defaults to client_secret_post and sends a secret you do not have.
const config = await client.discovery(
  new URL("https://simkl.com"),
  "YOUR_CLIENT_ID",
  undefined,
  client.None(),
  { algorithm: "oauth2" },   // read the OAuth 2.0 document, not OpenID Connect
);

// Server apps & services: pass the secret third, leave the fourth out --
// the client_secret_post default is correct for them.
```

Two things to get right: `algorithm: "oauth2"` (the default is `oidc`, that path returns `404`, and openid-client does not then try the OAuth 2.0 one), and the client auth method above.

<a id="python-authlib" />

## Python — Authlib

Discovery lives on the framework integrations, which share one API — swap `flask_client` for `django_client` or `starlette_client`.

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
from authlib.integrations.flask_client import OAuth

oauth = OAuth()
oauth.register(
    name="simkl",
    client_id="YOUR_CLIENT_ID",
    server_metadata_url="https://simkl.com/.well-known/oauth-authorization-server",
    client_kwargs={
        "scope": "media:read media:write",
        "code_challenge_method": "S256",        # S256 is the only method Authlib supports
        "token_endpoint_auth_method": "none",   # drop for Server apps & services,
                                                # and pass client_secret="..." instead
    },
)
```

<Note>
  **The plain `OAuth2Session` from `authlib.integrations.requests_client` does not read metadata.** It has no discovery support, so set `authorization_endpoint` and `token_endpoint` yourself there. `server_metadata_url` is a registry option, not a session attribute.
</Note>

<a id="java-spring-security" />

## Java — Spring Security

```yaml theme={"theme":{"light":"github-light","dark":"vesper"}}
spring:
  security:
    oauth2:
      client:
        registration:
          simkl:
            client-id:                    YOUR_CLIENT_ID
            client-secret:                YOUR_CLIENT_SECRET   # server apps only
            client-authentication-method: client_secret_basic  # "none" if you have no secret
            authorization-grant-type:     authorization_code
            redirect-uri:                 "{baseUrl}/login/oauth2/code/{registrationId}"
            scope:                        media:read,media:write
        provider:
          simkl:
            issuer-uri: https://simkl.com
```

One setting is enough. `ClientRegistrations.fromIssuerLocation()` walks three discovery paths in order, and ours — `/.well-known/oauth-authorization-server` — is the third; the two OpenID Connect paths ahead of it return `404`, which is a 4xx, so Spring falls through to ours and configures itself. Setting `authorization-uri` and `token-uri` explicitly instead is never wrong, and is what you want if you would rather not depend on a network fetch at startup.

<Warning>
  **No trailing slash.** `issuer-uri: https://simkl.com/` fails with *"Unable to resolve Configuration with the provided Issuer"*. RFC 8414 requires the document's `issuer` to match the one you requested character for character, and `https://simkl.com/` is not `https://simkl.com`.
</Warning>

If you configured a `client-secret`, you also need:

```java theme={"theme":{"light":"github-light","dark":"vesper"}}
var resolver = new DefaultOAuth2AuthorizationRequestResolver(repo, "/oauth2/authorization");
resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
```

Without it Spring sends no `code_challenge`, the authorize request is rejected, and the error arrives on your callback rather than at the point of configuration — which makes it look like a redirect problem.

## Anything else

If your library is not listed, it almost certainly works — fill in the table at the top and check three things:

1. **PKCE is on, and set to `S256`.** This is where most failures start, because many libraries treat PKCE as opt-in for clients that have a secret.
2. **The client auth method matches your registration.** An app with no secret must send only `client_id`; sending an empty secret is a different thing and fails.
3. **The two hosts are not collapsed into one.** Authorize is on `simkl.com`, token is on `api.simkl.com`.

For anything a library cannot express, the [OAuth flow walkthrough](/api-reference/oauth2-authorization-code) has the raw requests — including a complete runnable script that uses no dependencies at all.

## See also

<CardGroup cols={2}>
  <Card title="OAuth flow" icon="browser" href="/api-reference/oauth2-authorization-code">
    The raw requests, and a dependency-free script that runs the whole flow.
  </Card>

  <Card title="Discovery" icon="book-open" href="/api-reference/auth-v2#discovery">
    The RFC 8414 document, and the one flag some libraries need.
  </Card>

  <Card title="PKCE in AUTH V2" icon="shield-keyhole" href="/api-reference/oauth2-pkce">
    Generating the pair correctly, in five languages.
  </Card>

  <Card title="V1 client libraries" icon="clock-rotate-left" href="/api-reference/oauth-libraries">
    The tested V1 matrix. Note its discovery warning does not apply to V2.
  </Card>
</CardGroup>
