Skip to main content
When a user logs in to your app, Privy issues that user an access token that stores their authenticated session. You can configure Privy to store a user’s access token either with a browser’s local storage or as a HttpOnly cookie set on your app’s base domain. By default, Privy will store the user’s access token in local storage. Configuring cookies requires that your app have a stable base domain and that you set a DNS record for this domain. In kind, cookies are recommended for production applications only. While developing your integration, you can use Privy’s default setup of local storage to get started.

Enabling cookies

To configure your app to use cookies, follow the steps below:

1. Create separate development and production Privy app IDs

In the Privy Dashboard, create (at minimum) two Privy apps. Concretely, you should create one app for use in production environments only, and one app for use in development environments only. This step is critical, as once you enable cookies, your production app ID will only work in your production environment, and will error in all other environments.
The development process for all environments (production and development) will be the same on your end. The only difference is that you must use separate app IDs for each environment.
Next, follow the steps below, only for your production app ID. Do not complete them for your development app.

2. For your production app, register your production domain in the Privy Dashboard

In the Privy Dashboard, find your production app in the App Dropdown of the left sidebar. Then, navigate to the Configuration > App settings page > Domains tab for that app. Toggle on HttpOnly cookies. You’ll be prompted to add an app domain. This is the domain root of your web app (e.g. example.com). Do not include the protocol or www. Do not list a domain that is not a production domain. As a general rule, our team will not automatically approve domains that appear to be scoped to a sandbox environment. Example of such unsupported domains include *.vercel.app, *.railway.app, *.herokuapp.com, and *.amazonaws.com.

3. For your production domain, set the necessary DNS records

Once you’ve set your app’s domain in Configuration >> App settings >> Domains, Privy will display any required DNS records you must set for that for that domain. Go to the admin dashboard of your domain registrar and set the required DNS records. Once done, return to the HttpOnly cookies section in the Privy Dashboard and click the Refresh button on your domain. This will force Privy to re-verify if the correct DNS records have been set for that domain or not. Please note that it may take a few hours for DNS records to propagate before Privy can confirm that it is verified. This allows Privy’s servers to set a first-party cookie on your production domain. Once you’ve finished the steps above, Privy will review your request and update servers to begin setting cookies on your production app’s domain.
Once your domain is verified, the corresponding App ID can only be used on that exact production domain.
If using Cloudflare as your DNS records provider, make sure that the registered DNS record is not set to Proxied, and is set to DNS Only until the domain verification is complete.

App clients and cookies

By default an app serves HttpOnly cookies on a single cookie domain. With app clients, one app can serve cookies on multiple parent domains — one per client — while keeping a single shared set of users and wallets. This is useful when the same Privy app powers products on different domains (for example example.com and example.io). Each app client either:
  • Inherits the app’s cookie domain — the client uses whatever domain and cookie settings are configured at the app level.
  • Owns a client-specific cookie domain — the client has its own verified domain, independent of the app’s. Verifying or changing one client’s domain never disrupts another’s.
Cookies are only ever set once a base domain is verified. If a client inherits but no domain is set on the app or a client owns a domain that is not yet verified no cookies are set for that client.

Configuring cookies per client

In the Privy Dashboard, open Configuration > App settings > Domains and clients. The Configure by client card lists your app clients; use Create client to add one. For each client, choose how it resolves its cookie domain:
  • Use the app’s cookie domain — the client inherits the app-level domain. No additional DNS setup is needed.
  • Set up a different domain — configure a client-specific domain. You will be taken to the client’s page to enter the domain and complete DNS verification.
Verifying a client-specific domain follows the same flow as the app-level domain (see steps 2–3 above): set the domain, add the CNAME record shown, click Refresh, then add the TXT record Privy issues and Refresh again.
A client-specific cookie domain is locked once verified: a verified domain cannot be edited in place or switched back to inheriting.

Using an app client in your app

Pass the client’s client ID to PrivyProvider alongside your shared app ID. clientId is an existing prop. No other code changes are needed.
Allowed origins are configured per client and are not inherited from the app. When cookies are enabled for a client, that client’s domain must be listed in its allowed origins. Ensure that client id is set in client setup so that the correct allowed origins are enforced for that client.

Debugging DNS issues

CAA records block issuance

Some providers may require extra configuration in order to set up SSL for your base domain to work with Privy. If you are seeing the error “CAA records block issuance” in the Privy dashboard or you keep trying to set an acme_challenge and state resets, you might either:
  1. Already have CAA record that does not include one of the CAs Privy uses to issue SSL certs
  2. Need to explicitly set a CAA record
To resolve this, go to your provider and create a CAA record on your root domain (ie example.com, not including any subdomains). If there are already contents in the CAA record, append the following, otherwise create a new record containing the following:

The hostname is associated with a held zone

If you use Cloudflare as a DNS provider and have “held” your zone for security reasons, you will need to temporarily release the hold.

Using cookies in development

In both production and development (local, preview, staging) environments, Privy will set a cookie with the name privy-token to store your user’s session. Your app logic for handling the cookie (e.g. in your authorization middleware) does not need to handle different environments differently. The mechanics of how the cookie is set is the key difference between production and development environments. This is why you must only use your production App ID within your production environment. Concretely:
  • For your production app ID, once you have completed the steps above, Privy’s servers will set a cookie, only on the domain you have verified and any subdomains. Cookies will not be set on localhost.
  • For your development app ID(s), Privy’s client will automatically set a cookie on any domain you use this App ID on, including localhost. This allows you to use the same app logic around cookies across various environments. As a security precaution, client-set cookies for development have a shorter lifetime (7 days, versus 30 days for server-set cookies).
We recommend maintaining two apps, one for development and one for production. However, if you need to develop with your production App ID in a localhost environment, you can do so by using App Clients.

Server-side rendering

With cookies, when an authenticated user visits a page of your app, the request to fetch the page from your server will automatically include the user’s access token as a privy-token cookie. If your app uses server-side rendering (SSR), you can use the presence of this cookie (and other Privy cookies) to determine if the user is authenticated before your page is rendered on the client.

When the privy-token is present

Concretely, if the request to your server includes a valid privy-token, you should consider the user as authenticated and should handle them accordingly.

When the privy-token is absent

If the request to your server does not include a valid privy-token, the user might either:
  • be unauthenticated, and will need to login to become unauthenticated.
  • appear as unauthenticated, and will need to wait for the page to be rendered in their client before you can determine if they are authenticated.
The latter case generally occurs when an authenticated user steps away from your app for more than an hour, allowing the access token to expire, and returns to your app for the first time. In this case, the request to fetch the page from your server will not include a valid privy-token, as it has expired, but the privy-token will be refreshed imminently as soon as the page loads in the user’s browser. To handle this case, when the privy-token is missing in the request, you should instead wait for your app to load in the client, to allow their user’s authentication status to update correctly, before taking any actions based on their authentication status. This most commonly occurs in middleware setups that perform server-side routing. One solution for handling this flow is to set up your app and middleware like so:

Client-side setup

In your client, add a new page (e.g. /refresh) that implements the following:
  1. Call Privy’s getAccessToken method when the page loads. This ensures that whenever the user visits this page, their session is refreshed if they are authenticated.
  2. If getAccessToken returns a valid token, redirect the user to the path specified in a redirect_uri query parameter. Your middleware will populate this query parameter later.
  3. If getAccessToken returns null, redirect the user to your login page as they are not authenticated.

Middleware setup

In your middleware, when your backend receives a request to fetch a given page:
  1. If the request includes a privy-token that is valid, you can consider the user authenticated and apply your normal middleware.
  2. If the request does not include a privy-token but does include a privy-session cookie, the user may be authenticated, and you’ll need to refresh their session from the client before applying your middleware.
  3. To refresh the user’s session from the client, you can redirect the user to the /refresh page you set up above. As part of this, you should also pass the original route the user intended to visit as a query param (e.g. redirect_url) when you redirect them to /refresh. Per the client-side setup, this allows the user’s session to be refreshed and for them to be correctly redirected based on their authentication status.
Make sure to exclude the following from the above redirect middleware:
  1. The page at the /refresh path you setup: in this case, the user should be allowed to visit the /refresh page as their authentication status and redirect will be handled on that page in the client. Redirecting away from this page in your middleware may result in an infinite redirecting loop.
  2. Any page that includes the query parameter privy_oauth_code, privy_oauth_state, or privy_oauth_provider: these parameters are a required component of Privy’s OAuth login flow and applying a redirect will destructively erase them.
As an example, if you’re using NextJS, you might setup your middleware like so:
By design, Privy does not permit apps to refresh a user’s access token from the app’s server via the user’s refresh token. This is a standard security protection to limit the surface area of exposure of the refresh token.

Setting SameSite to Lax

Cookies set by Privy are by default set with the SameSite attribute set to Strict. This ensures that cookies are only sent on requests originating from the same site that set the cookie. However, you may wish to receive cookies on cross-site top-level navigations or safe requests methods (e.g. GET, HEAD, OPTIONS). In this case, you can toggle setting the SameSite attribute to Lax in the Privy Dashboard.
Setting SameSite=Lax sends your cookies on cross-site top-level navigations. If your app has any unprotected state-changing endpoints an attacker could leverage this to lure your users into making changes to their accounts.
In the Privy Dashboard, find your production app in the App Dropdown of the left sidebar. Then, navigate to the Configuration > App settings page > Domains tab for that app. Check the box next to Set SameSite to Lax under HttpOnly cookies.