Security
Security model, threat mitigation, and best practices for Phonelink integrations.
Security model
Phonelink uses signed JWTs (JSON Web Tokens) and cryptographic nonces to ensure phone verification results are authentic, untampered, and non-replayable.
JWT verification
Every verification result is a JWT signed by Phonelink's private keys. Your server verifies the signature using the public keys published at the JWKS endpoint:
https://phone.link/.well-known/jwks.jsonThe validate function handles this automatically using the jose library. It:
- Fetches and caches the public keys from the JWKS endpoint
- Verifies the JWT signature against the cached keys
- Validates the
iss(issuer) claim ishttps://phone.link - Validates the
aud(audience) claim matches your client ID - Checks the token has not expired (via the
expclaim)
Key rotation is handled transparently. The jose library automatically refetches the JWKS when it encounters an unknown key ID.
Nonce validation
The nonce prevents replay attacks — an attacker intercepting a valid token cannot reuse it.
Nonce lifecycle
Why this works:
- The nonce is generated by the client and stored locally (never sent to Phonelink in a way that could be intercepted and reused without the storage)
- Phonelink embeds it in the signed JWT, so it can't be altered
- The server checks that the nonce from the client matches the nonce in the token
- An attacker would need both the token AND access to the victim's
sessionStorageto replay the verification
Single-use guarantee
On the web, the nonce is removed from sessionStorage immediately after getResult() reads it. This means:
- Refreshing the callback page returns
null(the nonce is gone) - Each verification flow uses a unique nonce
- Old tokens cannot be replayed even if intercepted
On Expo, the nonce is held in memory for the duration of the verify() call and is never persisted to disk.
Audience validation
The aud (audience) claim in the JWT is set to your client ID. The server checks this to ensure the token was issued for your application specifically.
This prevents token misuse across different Phonelink clients. A token issued for client-A will fail verification when checked against client-B.
Token expiry
Tokens have a limited lifetime enforced via the exp claim. The jose library automatically rejects expired tokens during verification. You do not need to check expiry manually.
Best practices
Always verify server-side
Never verify tokens in client-side code. The web client (phonelink/web) intentionally does not verify JWTs — it only passes the raw token to your server. All cryptographic validation must happen on your server using phonelink/validate.
Always validate the nonce
Always forward the nonce from the client to your server alongside the token. Never skip nonce validation, even in development. The validate function requires the nonce as a parameter and will throw if it doesn't match.
Use HTTPS
Ensure your callback URL and server endpoints use HTTPS. This prevents token interception in transit.
Don't store tokens long-term
Phonelink tokens are short-lived and single-purpose. Extract the phone number (phone_e164) from the verified payload and store that in your database. Don't store the raw JWT.
Validate before trusting
Don't extract claims from the JWT without verification. Always call validate first. Only use properties from the returned PhonelinkPayload object.