Skip to main content
Developers/Before you go live

Security checklist

Go through this list with your integration open next to it. Most items are one line of code; each one closes a way that sign-in integrations are attacked.

Secrets

  • The client secret lives only on your server, in a secret store or environment variable. It is not in front-end code, a mobile app, a repository or a log.
  • You know how to rotate it: add a second secret in the console, deploy it, then retire the old one. Both work in between.
  • The webhook signing secret (whsec_...) and your cookie-signing secret are stored the same way.

The sign-in request

  • Every request has a fresh random state, a fresh nonce and a fresh PKCE verifier, made with a cryptographic random source.
  • They are kept in an HttpOnly, Secure, SameSite=Lax cookie scoped to your auth path, signed or encrypted, that lives about 10 minutes, or in your server-side session.
  • The redirect URI is https and registered exactly. No wildcards. localhost only on a development-mode app.
  • The button links to your own start route. Nothing builds an /authorize URL in the browser.

The callback

  • state is compared with the stored value before anything else, and used once. A missing or different state ends the attempt.
  • The code is exchanged on your server with your client credentials and the stored verifier. The same redirect_uri is sent as in the request.
  • The ID token is validated: alg is ES256, the signature checks against a key from https://id.avonao.com/.well-known/jwks.json, iss is https://id.avonao.com, aud is your client ID, exp and iat are sane, nonce matches.
  • The keys are cached for at most 10 minutes and fetched again once on an unknown kid. They are not pinned and not copied into your code.
  • Your own session is created only after all of that succeeded, with a new session id. A failed sign-in leaves no session behind.
  • Both routes are rate limited.
  • Tokens and codes are not logged and not put in URLs that you pass on. You rarely need to keep the ID token or access token: take what you need from the claims and drop them.

Accounts

  • Users are keyed by sub (a unique column), never by email. See Pairwise ids.
  • An existing local account is attached by email only when you verified that address on the local account too, and never when it already has a different sub.
  • Your callback handles access_denied and every other error with a fixed message and a retry button, never in a loop, and never printing error_description as it came. See Errors.

Webhooks

  • The signature is checked on the raw body, compared in constant time, and a timestamp older than 5 minutes is refused.
  • The handler is idempotent and de-duplicates on avonao-delivery. It answers a 2xx within 5 seconds and ignores event types it does not know.
  • member.deleted erases that member's data, and grant.revoked ends their Avonao ID sessions. See Webhooks.

Going live

  • Your domain is verified, every redirect URI and the sign-in start URL are https on it, and the app has a Privacy Policy URL.
  • You tested with a teammate in development mode, and with a member from outside your business after you went live.
  • Your own sign-out ends your session, and "Manage your Avonao ID" links to https://id.avonao.com/dashboard.
  • If you load the button's stylesheet or icon from id.avonao.com, your Content Security Policy allows it in style-src and img-src; or you host both yourself. See the Button kit.
One account
Avonao ID

One Avonao ID. Every platform. Your call.

Create your account in a minute with a passkey, then connect the platforms you use.

Avonao ID account