Skip to content

Guide

eSigKit accepts two authentication methods on every authenticated route: Cognito ID tokens (PKCE flow, browser apps) or per-tenant API keys (server-to-server). They’re interchangeable — pick whichever fits the calling context.

Decision matrix

Use caseMethodHeader
Browser app, Chrome extension, mobileCognito JWT (PKCE)Authorization: Bearer <ID-token>
Server backend, CI, integration testsAPI keyAuthorization: Bearer esk_… or X-Api-Key: esk_…
Webhook receivers (e.g. Stripe → us)HMAC signaturen/a — verified per-route

Two Cognito app clients

eSigKit’s user pool ships two app clients with different security profiles. Customers should always use the first; the second is documented for completeness.

Customer clientAdmin client
Nameesigkit-app-client-{stage}esigkit-admin-client-{stage}
Used byDashboard at app.esigkit.com, Chrome extension, mobileStaff admin app at admin.esigkit.com
Refresh-token TTL30 days7 days (smaller blast radius)
MFA enforcedOptional (TOTP available)Required (Pre-Auth Lambda gate)
Scopesopenid email profile aws.cognito.signin.user.adminSame + custom:role=super-admin
Callback URLsapp.esigkit.com, Chrome extension originadmin.esigkit.com only

eSigKit verifies ID tokens, not access tokens. The tenancy claims (custom:orgId, custom:role) ride on the ID token only — sending an access token returns 401.

PKCE flow (browser / extension)

Standard OAuth 2.0 Authorization Code with PKCE. The dashboard does this for you via aws-amplify; if you’re building a custom client:

  1. Generate a random code_verifier (43-128 chars URL-safe).
  2. Compute code_challenge = base64url(sha256(code_verifier)).
  3. Redirect the user to:
https://auth.esigkit.com/oauth2/authorize
  ?client_id=YOUR_APP_CLIENT_ID
  &response_type=code
  &scope=openid+email+profile
  &redirect_uri=YOUR_CALLBACK_URL
  &code_challenge_method=S256
  &code_challenge=<computed>
  1. Exchange the returned code for tokens:
curl -X POST "https://auth.esigkit.com/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_APP_CLIENT_ID" \
  -d "code=GOOGLE_AUTH_CODE" \
  -d "redirect_uri=YOUR_CALLBACK_URL" \
  -d "code_verifier=<from step 1>"
  1. Use id_token (NOT access_token) as the bearer:
curl "https://api.esigkit.com/v1/orgs/$ESIGKIT_ORG_ID" \
  -H "Authorization: Bearer $ID_TOKEN"

Token TTLs: ID + access tokens = 1 hour; refresh tokens = 30 days (customer) or 7 days (admin). Use the refresh token to mint new ID tokens silently.

API key flow (server-to-server)

Mint a key via the dashboard’s Settings → API Keys page (recommended) or via POST /v1/orgs/{orgId}/api-keys. Both header conventions work:

# Stripe-style — works because we accept Bearer tokens that start with `esk_`
curl "https://api.esigkit.com/v1/orgs/$ESIGKIT_ORG_ID/users" \
  -H "Authorization: Bearer $ESIGKIT_TOKEN"

# Or the conventional X-Api-Key header
curl "https://api.esigkit.com/v1/orgs/$ESIGKIT_ORG_ID/users" \
  -H "X-Api-Key: $ESIGKIT_TOKEN"

Key format: esk_<stage>_<random>. The stage prefix lets you tell at a glance which environment the key belongs to (esk_prod_… vs esk_test_…). The random portion is 256 bits of entropy, base64url-encoded.

API keys are bound to the admin role only — super-admin actions are JWT-only and aren’t accessible via API key.

Token rotation

Recommended cadence: quarterly minimum. Rotation pattern that avoids downtime:

  1. Mint a new key (POST /api-keys).
  2. Roll the new key out to your callers (env var swap, secrets-manager update).
  3. Verify via dashboard’s API-keys list that the new key is being used (each key tracks lastUsedAt).
  4. Revoke the old key (DELETE /api-keys/{keyId}).

For JWTs, rotation is automatic via the refresh-token flow — your client silently mints fresh ID tokens every hour without user interaction.