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

# Security Best Practices

> Customer funds are at stake. These requirements are not optional.

## PKCE is mandatory

Every authorization request must include `code_challenge` and `code_challenge_method=S256`. Generate a **fresh** `code_verifier` (43–128 URL-safe characters) per authorization request. `plain` challenges and missing challenges are rejected.

PKCE ensures that an attacker who intercepts the authorization code cannot redeem it without also possessing the verifier, which never leaves your backend.

## `state` is required for CSRF protection

Generate at least 16 bytes from a cryptographic RNG, bind the value to the user's session, and reject any callback where the returned `state` does not match. Without this guard, an attacker can plant a known authorization code on someone else's session.

## `nonce` for OIDC ID tokens

When you request `openid`, include a `nonce` parameter in the authorization request and validate it inside the `id_token`. This protects against ID-token replay attacks.

## `redirect_uri` must be exact

Register only redirect URIs your application controls. Production URIs must use HTTPS — HTTP is only allowed for `localhost`. Busha compares `redirect_uri` byte-for-byte:

* `https://app.example.com/cb` and `https://app.example.com/cb/` are **different URIs**.
* Port, scheme, path casing, and trailing slashes all matter.
* A mismatch is rejected immediately.

## `client_secret` belongs in your backend

Never place `client_secret` in:

* A mobile application
* A single-page application
* A browser extension
* Any code that ships to users

Store it in a secret manager (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager). If it is ever compromised, rotate it immediately using **Rotate Secret** on your app's dashboard page.

## Always verify access tokens before trusting them

Anyone can base64-decode a JWT and read its claims. None of those claims are trustworthy until you have verified the RS256 signature against the JWKS. See [Token handling → Validating access tokens](./tokens#validating-access-tokens) for the full validation checklist.

## Secure refresh token storage

Refresh tokens must be stored:

* **Encrypted at rest** with row-level encryption keyed per user.
* **Server-side only** — never in browser cookies or `localStorage`.
* Persisted **atomically** — write the new pair before discarding the old one.

## Revoke on disconnect

When a user disconnects your integration, call `POST /oauth2/revoke` with `token_type_hint=refresh_token`. This immediately prevents new tokens from being minted. The currently-issued access token continues to be valid until `exp`, but no new session can be started.

## Never log sensitive values

| Never log            | Safe to log         |
| -------------------- | ------------------- |
| `client_secret`      | `client_id`         |
| `access_token`       | `jti` (JWT ID)      |
| `refresh_token`      | `sub` (user UUID)   |
| `code_verifier`      | Scope list          |
| Authorization `code` | Request / trace IDs |
| `id_token`           |                     |
