Skip to main content
A quick way to get started during development before implementing custom auth is to use Development Tokens
When you set up custom authentication, you define the fetchCredentials() function in your backend connector to retrieve a JWT from your backend application API, making use of your existing app-to-backend authentication:

Custom Authentication Flow

The process is as follows:
  1. Your client app authenticates the user using the app’s authentication provider (either a third-party authentication provider or a custom one) and typically gets a session token.
  2. The client makes a call to your backend API (authenticated using the above session token), which generates and signs a JWT for PowerSync. (You define the fetchCredentials() function in your backend connector so that it makes the API call, and the PowerSync Client SDK automatically invokes fetchCredentials() as needed).
    1. For example implementations of this backend API endpoint, see Custom Backend Examples
  3. The client connects to the PowerSync Service using the above JWT (this is automatically managed by the PowerSync Client SDK).
  4. The PowerSync Service verifies the JWT.

JWT Requirements

Requirements for the signed JWT:
  1. The JWT must be signed using a key in the JWKS URL (Option 1) or the HS256 key (Option 2)
  2. JWT must have a kid matching that of the key.
  3. The aud of the JWT must match the PowerSync instance URL (for Cloud) or one of the audiences configured in client_auth.audience (for self-hosted).
    1. To get the instance URL when using PowerSync Cloud: In the PowerSync Dashboard, click Connect in the top bar and copy the instance URL from the dialog.
    2. Alternatively, specify a custom audience in the instance settings (Cloud) or in your config file (self-hosted).
  4. The JWT must expire in 60 minutes or less. Specifically, both iat and exp fields must be present, with a difference of 3600 or less between them.
  5. The user ID must be used as the sub of the JWT.
  6. Additional fields can be added which can be referenced in Sync Rules parameter queries or Sync Streams (as auth.parameters()).

Option 1: Asymmetric JWTs — Using JWKS URL

A key pair (private + public key) is required to sign and verify JWTs. The private key is used to sign the JWT, and the public key is advertised on a public JWKS URL. Requirements for the key in the JWKS URL:
  1. The URL must be a public URL in the JWKS format.
    1. We have an example endpoint available here — ensure that your response looks similar.
  2. Supported signature schemes: RSA, EdDSA and ECDSA.
  3. Key type (kty): RSA, OKP (EdDSA) or EC (ECDSA).
  4. (alg):
    1. RS256, RS384 or RS512 for RSA
    2. EdDSA for EdDSA
    3. ES256, ES384 or ES512 for ECDSA
  5. Curve (crv) - only relevant for EdDSA and ECDSA:
    1. Ed25519 or Ed448 for EdDSA
    2. P-256, P-384 or P-512 for ECDSA
  6. A kid must be specified and must match the kid in the JWT.
Refer to this example for creating and verifying JWTs for PowerSync authentication. Since there is no way to revoke a JWT once issued without rotating the key, we recommend using short expiration periods (e.g. 5 minutes). JWTs older than 60 minutes are not accepted by PowerSync.

Rotating Keys

If a private key is compromised, rotate the key on the JWKS endpoint. PowerSync refreshes the keys from the endpoint every couple of minutes, after which old tokens will not be accepted anymore. There is a possibility of false authentication errors until PowerSync refreshes the keys. These errors are typically retried by the client and will have little impact. However, to periodically rotate keys without any authentication failures, follow this process:
  1. Add a new key to the JWKS endpoint.
  2. Wait an hour (or more) to make sure PowerSync has the new key.
  3. Start signing new JWT tokens using the new key.
  4. Wait until all existing tokens have expired.
  5. Remove the old key from the JWKS endpoint (or config file for self-hosted instances).

PowerSync Cloud Configuration

  1. In the PowerSync Dashboard, select your project and instance and go to the Client Auth view.
  2. Configure your JWKS URI and audience settings.
  3. Click Save and Deploy to apply the changes.

Self-Hosted Configuration

You can configure authentication using either:
  • A JWKS URI endpoint
  • Static public keys in the configuration file
This can be configured via your config.yaml:
config.yaml
client_auth:
  # Option 1: JWKS URI endpoint
  jwks_uri: http://demo-backend:6060/api/auth/keys

  # Option 2: Static collection of public keys for JWT verification
  # jwks:
  #   keys:
  #     - kty: 'RSA'
  #       n: '${PS_JWK_N}'
  #       e: '${PS_JWK_E}'
  #       alg: 'RS256'
  #       kid: '${PS_JWK_KID}'

  audience: ['powersync-dev', 'powersync']

Option 2: Symmetric JWTs — Using HS256

PowerSync supports HS256 symmetric JWTs.

Generating a Shared Secret

You can generate a shared secret in the terminal using the following command:
openssl rand -base64 32

Base64 URL Encode the Shared Secret

Once you’ve generated the shared secret, you will need to Base64 URL encode it before setting it in the PowerSync instance Client Auth configuration. You can use the following command to Base64 URL encode the shared secret:
echo -n "your-value-here" | base64 -w 0 | tr '+/' '-_' | tr -d '='

Set the Shared Secret in the PowerSync Instance

  1. Go to the PowerSync Cloud Dashboard and select your project and instance.
  2. Go to the Client Auth view.
  3. Find the section labeled HS256 Authentication Tokens (ADVANCED) and click + button to add a new token.
  4. Set the KID to a unique identifier for the token (you’ll use the same KID to sign the token). Set the Shared Secret to the Base64 URL encoded shared secret.
  5. Click Save and Deploy.

Generate New JWTs Using the KID and Shared Secret

Using your newly-created shared secret, you can generate JWT tokens in your backend using the same KID you set in the PowerSync Service configuration. Here’s a example TypeScript function using the jose library:
import * as jose from 'jose';

export const generateToken = async (payload: Record<string, unknown>, userId: string) => {
  return await new jose.SignJWT(payload)
    .setProtectedHeader({ alg: 'HS256', kid: 'your-kid' })
    .setSubject(userId)
    .setIssuer('https://your-domain.com')
    .setAudience('https://your-powersync-instance.com')
    .setExpirationTime('60m')
    // Note: The shared secret should be read from a secure source or environment variable and not hardcoded.
    .sign(Buffer.from('your-base64url-encoded-shared-secret', 'base64url'));
};
This JWT can then be used to authenticate with the PowerSync Service. In your fetchCredentials() function, you will need to retrieve the token from your backend API.