# SDK Authentication

The SDK does not authenticate an end user itself. The issuer authenticates its user in its own backend, then gives the SDK a short-lived session through an issuer-controlled proxy.

Production SDK integrations use signed JWT client assertions. The issuer proxy signs every widget-session mint request with its private key, and Percents verifies that assertion with the issuer's registered JWKS. This is the required production authentication method; do not use a shared secret or issuer API token for this flow.

## Trust Boundaries

```text
Issuer host app and browser
  -> issuer proxy: opaque proxy session token
  -> Percents: no issuer credential and no widget token

Issuer proxy
  -> Percents: short-lived, CHG-scoped widget token
```

The browser and hosted iframe must never receive an issuer-wide Percents API token or the Percents widget token. The opaque proxy session token is meaningful only to the issuer proxy.

## Session Flow

1. The issuer backend authenticates the current user with its existing identity system.
2. The issuer backend resolves that user or business to a persisted Percents cardholder group (`chgId`). Do not accept a client-side-supplied `chgId`.
3. The issuer proxy obtains a short-lived widget token from Percents, scoped to that CHG, and stores it server-side.
4. The proxy creates an opaque, expiring proxy session and returns its token, the proxy API URL, and the widget URL to the SDK.
5. The hosted iframe sends its requests to the issuer proxy with the proxy-session token. The proxy validates the session, forwards only widget-allowed operations with the scoped widget token, and removes server-only data from responses.


Persist the issuer user/business-to-CHG mapping. Percents webhooks identify the CHG, so the issuer needs that durable mapping to route follow-up work or notifications correctly.

For integration setup and the session response shape, see the [SDK Quickstart](/percents-api/embedded-widget/quickstart). For iframe message and session boundaries, see [SDK Security and Sessions](/percents-api/embedded-widget/security-sessions).

## Production Mint Authentication

The issuer proxy authenticates itself to Percents by sending a signed JWT assertion with every widget-session mint request. The assertion is a short-lived proof that the issuer proxy controls its registered private key and is authorized to mint a session for one cardholder group.

### Authentication Flow

```mermaid
sequenceDiagram
  participant Browser as Issuer host app and browser
  participant Proxy as Issuer proxy
  participant Percents as Percents API
  participant JWKS as Issuer JWKS endpoint
  participant Iframe as Percents iframe

  Browser->>Proxy: Request widget proxy session using issuer login session
  Proxy->>Proxy: Authenticate user and resolve CHG
  Proxy->>Proxy: Sign mint assertion with issuer private key
  Proxy->>Percents: POST widget session with assertion and chgId
  Percents->>JWKS: Fetch public key selected by kid (when needed)
  JWKS-->>Percents: JWKS public keys
  Percents->>Percents: Verify signature, issuer, audience, time, jti, and CHG ownership
  Percents-->>Proxy: Short-lived CHG-scoped widget token
  Proxy->>Proxy: Store widget token and create opaque proxy session
  Proxy-->>Browser: widgetUrl, proxyApiBaseUrl, opaque proxySessionToken
  Browser->>Iframe: Mount iframe and provide proxy session token
  Iframe->>Proxy: Widget request with opaque proxy session token
  Proxy->>Percents: Allowed widget request with server-side widget token
```

1. The issuer host app asks its own backend for a widget proxy session. Its existing issuer login session authenticates this request.
2. The issuer proxy authenticates the user and resolves the user or business to a persisted Percents cardholder group (`chgId`). Never accept a client-side-supplied `chgId` as the source of truth.
3. The proxy creates a new signed mint assertion using its private key. The private key stays in issuer-controlled server-side infrastructure.
4. The proxy sends the assertion and `chgId` to Percents to mint a widget session.
5. Percents selects the issuer's public key using the assertion's `kid`, retrieving the issuer's JWKS as needed.
6. Percents validates the assertion's signature, algorithm, issuer, audience, expiry, issue time, unique ID, and ownership of the requested CHG.
7. Percents returns a short-lived widget token scoped to that CHG and widget-safe operations. The proxy stores this token server-side.
8. The proxy returns an opaque proxy-session token to the SDK. The browser and iframe use that token only with the issuer proxy; they never receive the signed assertion, private key, issuer API token, or Percents widget token.


### Mint Request

The issuer proxy mints a Percents widget session with:

```http
POST <percents-api-base-url>/api/v2/widget/session
Authorization: Bearer <signed-mint-assertion>
Content-Type: application/json

{
  "chgId": "chg_..."
}
```

`<signed-mint-assertion>` is an [RS256 JSON Web Signature (JWS)](https://www.rfc-editor.org/rfc/rfc7515) carrying a [JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519). It is sent only from the issuer proxy to Percents.

### Assertion Fields

The following fields are required for every signed mint assertion:

| Location | Field | Value | Why Percents validates it |
|  --- | --- | --- | --- |
| Protected header | `alg` | `RS256` | Ensures Percents uses the configured asymmetric signing algorithm and never accepts an unexpected algorithm. |
| Protected header | `kid` | Identifier for a public key in the issuer's [JWKS](https://www.rfc-editor.org/rfc/rfc7517) | Lets Percents select the correct public key and supports safe key rotation. |
| JWT claim | `iss` | Issuer identifier supplied by Percents during onboarding | Identifies the issuer making the assertion. Percents derives the issuer from this verified claim; do not send a separate issuer identifier in the request body. |
| JWT claim | `chgId` | The Percents cardholder group being authorized | Binds the assertion to one CHG. It must exactly match the request body and belong to the issuer identified by `iss`. |
| JWT claim | `aud` | Exact audience value supplied by Percents for this issuer and environment | Identifies the intended recipient: the Percents SDK-session endpoint. It prevents an assertion for another API or environment from being reused here. |
| JWT claim | `iat` | Time the assertion was issued | Allows Percents to reject assertions that are too old. |
| JWT claim | `exp` | Expiration time, no more than five minutes after `iat` | Limits the time in which a stolen assertion could be used. |
| JWT claim | `jti` | A new, unique assertion ID, such as a UUID | Allows Percents to reject a replay of an assertion that has already been used. |


### Expected Audience

`aud` is the standard JWT **audience** claim defined in [RFC 7519 section 4.1.3](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3). It identifies the intended recipient of a token. In this flow, the recipient is the Percents SDK-session endpoint—not the issuer, the SDK, or the iframe.

Percents supplies the exact, environment-specific audience value during onboarding. The issuer proxy must include that exact value in every mint assertion, and Percents rejects an assertion with any other value. This prevents an assertion created for another API, customer integration, or environment from being accepted by the widget-session mint endpoint. This follows the JWT best practice to validate issuer and audience claims ([RFC 8725 section 3.9](https://www.rfc-editor.org/rfc/rfc8725#section-3.9)).

### Production Onboarding and Key Rotation

1. Obtain the issuer identifier and expected audience from your Percents account manager.
2. Generate and protect an RS256 private key in issuer-controlled server-side infrastructure. Do not expose it to a browser, mobile app, or client-side configuration.
3. Publish the corresponding public key in a stable HTTPS JWKS endpoint, typically `https://issuer.example.com/.well-known/jwks.json`.
4. Provide Percents with that JWKS URL. Percents uses it to verify assertions from the issuer proxy.
5. When rotating keys, publish the new public key with a new `kid` before the proxy begins signing with its corresponding private key. Keep the prior public key available until all assertions signed with it have expired.


Percents resolves the public key selected by `kid`, verifies the signature and claims, rejects a `jti` that has already been used during the assertion's validity window, and confirms that the CHG belongs to the issuer identified by `iss`. Percents then returns a short-lived widget token restricted to that CHG and the widget-safe operations.

JWKS discovery supports signing-key rotation without sharing a secret or manually registering a new key.

## Integration Requirements

- Keep issuer API tokens, signing keys, and widget tokens in server-side infrastructure only.
- Use an expiring, opaque proxy session token in the browser; validate it on every proxy request.
- Allowlist only the widget routes the proxy needs. Do not turn the proxy into a general-purpose Percents API relay.
- Enforce the server-resolved CHG on every proxied request and remove `chgId` from browser-visible responses.
- Store proxy sessions and widget tokens in a shared, durable server-side store when using multiple proxy instances.
- Restrict browser origins and iframe framing with appropriate CORS and CSP `frame-ancestors` configuration.