# Sign users in with SecurySign

SecurySign sign-in uses OpenID Connect (OIDC). Redirect the browser to the authorization endpoint; after sign-in, the callback receives `code` and `state`. The backend exchanges the code for tokens. Customers can sign in with Google or an enterprise provider configured through the [IdP guide](#/docs/idp-integration-guide).

The resulting access token authenticates [hash signing](#/docs/api-hash-signing), [PAdES signing](#/docs/api-pades-signing), [webhooks](#/docs/api-webhooks), [certificates](#/docs/api-certificates) and [encryption](#/docs/api-encryption) requests on that user's behalf.

## What you need

Before starting, you'll need:

- An approved relying party (RP) with its client ID and OIDC client secret. See the [RP integration guide](#/docs/rp-integration-guide).
- Your callback URL registered as a redirect URI on the RP.

OIDC libraries can discover the authorization, token and signing-key endpoints at `https://securysign.com/auth/realms/signa/.well-known/openid-configuration`.

## 1. Send the user to authorize

```text
https://securysign.com/auth/realms/signa/protocol/openid-connect/auth
  ?client_id=signa-rp-42
  &redirect_uri=https://app.example.com/auth/callback
  &response_type=code
  &scope=openid
  &state=<random>
  &kc_idp_hint=google
```

| Parameter | Required | Value |
|---|---|---|
| `client_id` | Yes | Your client ID, for example `signa-rp-42`. |
| `redirect_uri` | Yes | Your callback URL, exactly as registered on your RP, URL-encoded. |
| `response_type` | Yes | `code` |
| `scope` | Yes | `openid`, followed by any other scopes assigned to your client, separated by spaces. |
| `state` | Yes | A random value you generate for this sign-in and store in the user's session. |
| `kc_idp_hint` | No | `google` to take your user straight to Google, or the alias of your enterprise provider. Without it, your user sees the SecurySign sign-in page. |

## 2. Exchange the code

At the callback, compare `state` with the random value saved in the user's session. Exchange `code` on the backend using the same redirect URI as the authorization request:

```bash
curl -X POST https://securysign.com/auth/realms/signa/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  --data-urlencode "client_id=signa-rp-42" \
  --data-urlencode "client_secret=$OIDC_CLIENT_SECRET" \
  --data-urlencode "code=$CODE" \
  --data-urlencode "redirect_uri=https://app.example.com/auth/callback"
```

Set `$OIDC_CLIENT_SECRET` to the secret issued at RP approval and `$CODE` to the callback's query parameter. Each authorization code can be used once. The response's expiry fields determine when the tokens need refreshing.

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiIs…",
  "expires_in": 300,
  "refresh_token": "eyJhbGciOiJIUzI1NiIs…",
  "refresh_expires_in": 1800,
  "token_type": "Bearer",
  "id_token": "eyJhbGciOiJSUzI1NiIs…",
  "scope": "openid email profile"
}
```

Store `access_token` on the backend and send it as `Authorization: Bearer <access_token>` on requests for that user. `expires_in` is the remaining token lifetime in seconds.

| Response | Cause | Resolution |
|---|---|---|
| `400 {"error": "invalid_grant", "error_description": "Code not valid"}` | The code expired or was already used | Send the user through sign-in again |
| `400 {"error": "invalid_grant", "error_description": "Incorrect redirect_uri"}` | `redirect_uri` differs from the one in the authorization request | Send the same `redirect_uri` in both requests |
| `401 {"error": "unauthorized_client", "error_description": "Invalid client or Invalid client credentials"}` | Your client ID or OIDC client secret is wrong | Send the OIDC client secret from your RP |

## 3. Read the claims

| Claim | What you get |
|---|---|
| `sub` | The user's permanent identifier. Key your users on it. |
| `email`, `email_verified` | The user's email address, and whether their identity provider verified it. Rely on the address only when `email_verified` is `true`. |
| `preferred_username` | The user's username at SecurySign |
| `name`, `given_name`, `family_name`, `picture` | Profile data, when the user's identity provider sends it |
| `signa_certificate_url`, `signa_certificate_serial`, `signa_certificate_subject` | The user's signing certificate, when you have the `signa-certificate` scope; see the [Verification API](#/docs/api-certificates#3-read-the-signers-certificate). |
| `visible_signature_url`, `visible_signature_id`, `visible_signature_sha256` | The user's drawn signature image, when you have the `signa-visible-signature` scope. |

Certificate and signature-image downloads require the access token of the same customer whose URLs appear in the claims.

## Sign in from a native app

Native apps open sign-in in the system browser and receive the callback through an app link. Registering a native redirect URI creates a public client named `<client_id>-native`, for example `signa-rp-42-native`. It uses Proof Key for Code Exchange (PKCE) with `S256`: the app sends a code challenge during authorization and the matching verifier during exchange.

Native sign-in also requires:

- An approved RP, and your domain (for example `example.com`) as one of its [authorised signing origins](#/docs/rp-integration-guide#3-authorise-additional-signing-origins), as `https://example.com`.
- Your domain allowed as a WebAuthn relying party ID at SecurySign; ask support if the files below answer `Unknown domain`.
- Your Android package name and signing certificate SHA-256 fingerprints, or your Apple Team ID and bundle identifier.

### Register your app

Register the app under **Mobile apps** on the [RP dashboard](https://cloud.securysign.com/#/rp/dashboard), providing the package or bundle identifier, WebAuthn domain, signing identity and callbacks. The same registration can be submitted through [`POST /rp/mobile-apps`](#/docs/api-relying-parties):

| Field | Android | iOS |
|---|---|---|
| WebAuthn RP ID | Your domain, `example.com` | Your domain, `example.com` |
| Package name / bundle identifier | `com.example.clientx` | `com.example.clientx` |
| Fingerprints / Team ID | Every SHA-256 fingerprint that signs a build you ship: the Play App Signing key for store builds, your debug key for testing | Your 10-character Team ID |
| Sign-in redirect URIs | Up to five, matched exactly: `https://example.com/app/callback` (an App Link) or your app ID as the scheme, `com.example.clientx:/oauth2redirect` | The same, with the https form as a Universal Link |
| Universal Link paths | — | Other paths your app opens, such as `/sign/*` (optional) |

Use a dedicated HTTPS path for the native callback, such as `/app/callback`, separate from the web callback. A small page at that path can handle visits on devices without the app installed.

The first native redirect URI creates `signa-rp-42-native`. Later mobile-app changes update its callbacks in the same request.

### Serve the association files from your domain

Association files connect the app to its HTTPS callbacks and WebAuthn credentials. SecurySign generates them from the registration. Proxy them through the app's domain at the following paths, returning `200` with `Content-Type: application/json`:

```nginx
location = /.well-known/assetlinks.json {
    proxy_pass https://securysign.com/api/well-known/example.com/assetlinks.json;
    proxy_ssl_server_name on;
}
location = /.well-known/apple-app-site-association {
    proxy_pass https://securysign.com/api/well-known/example.com/apple-app-site-association;
    proxy_ssl_server_name on;
}
```

Validate the Android statement list and Apple App Site Association file after publishing them. Android checks App Links during installation, so reinstall the test build to refresh the result. `adb shell pm get-app-links com.example.clientx` reports the domain as `verified` when association succeeds.

On iOS, include `applinks:example.com` and `webcredentials:example.com` in Associated Domains. On Android, declare the callback's HTTPS App Link with `android:autoVerify="true"`.

### Send the user to authorize

Use a Custom Tab on Android or `ASWebAuthenticationSession` on iOS; AppAuth can manage the flow on either platform. Generate a random `code_verifier` for each attempt and send its base64url-encoded SHA-256 digest as `code_challenge`:

```text
https://securysign.com/auth/realms/signa/protocol/openid-connect/auth
  ?client_id=signa-rp-42-native
  &redirect_uri=https://example.com/app/callback
  &response_type=code
  &scope=openid
  &state=<random>
  &code_challenge=<base64url(SHA-256(code_verifier))>
  &code_challenge_method=S256
  &kc_idp_hint=google
```

### Exchange the code in the app

At the app callback, verify `state` against the saved value. Exchange the returned `code` with the saved `code_verifier` and the same redirect URI:

```bash
curl -X POST https://securysign.com/auth/realms/signa/protocol/openid-connect/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  --data-urlencode "client_id=signa-rp-42-native" \
  --data-urlencode "code=$CODE" \
  --data-urlencode "code_verifier=$CODE_VERIFIER" \
  --data-urlencode "redirect_uri=https://example.com/app/callback"
```

Native-client tokens contain `azp: "signa-rp-42-native"`. SecurySign maps that client to the RP's granted scopes and allowances. If the backend validates `azp`, include the registered client IDs for both web and native sign-in.

## Troubleshooting

| Symptom | Fix |
|---|---|
| `invalid_scope` | Request only scopes assigned to your client. One unassigned scope fails the whole login. |
| `invalid_redirect_uri` | Register the exact callback URL as a redirect URI on your RP |
| Blank `email` after login | Your user's identity provider shared no email. For your own SAML or OIDC provider, map an email attribute; see the [IdP integration guide](#/docs/idp-integration-guide#connect-a-saml-20-provider) |
| `Token missing subject claim` from a SecurySign API | Your client's access tokens carry no `sub`. Contact support to add the `basic` scope to your client. |
| Registering an app fails with `… needs https://example.com as one of this RP's authorized signing origins` | Request `https://example.com` under **Authorized signing origins** on the RP dashboard, then register the app again |
| `Native app sign-in is not enabled on this deployment yet` | Register the app without redirect URIs, and ask support to enable native sign-in |
| `Missing parameter: code_challenge_method` | Send `code_challenge` and `code_challenge_method=S256`; the native client requires PKCE |
| `invalid_redirect_uri` from the native client | Send a redirect URI registered on one of your apps, character for character |
| The redirect opens the browser instead of your app | Your domain is not serving the association files, or the app was installed before they were live. Check both validators, then reinstall |

See the [Sign-in reference](#/docs/api-sign-in-oidc) for authorization parameters and token-exchange fields.
