Skip to content

Configuring cookies

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.

INFO

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 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 the Dashboard, 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.

INFO

Once your domain is verified, the corresponding App ID can only be used on that exact production domain.

App clients and cookies

Each app can only have one cookie domain. If you share an app ID across web and mobile environments, you can use app clients to conditionally enforce cookies depending on the environment. For example, you can have an app client that enforces cookies for your web app, and an app client that does not enforce it for your mobile app.

To toggle cookie settings for different app clients, first set your HttpOnly Cookies and an app domain. Then, go to the Settings page > Clients tab, and find the App client you’d like to enable cookies for. Select “edit”, set the cookies toggle to Enabled, and save.

If you enable cookies in an app client, but no base domain is set on your app, no cookies will be set.

TIP

Within an app client, you can choose to enable or disable cookies. If you enable cookies for any client, they will be set on the domain that is configured as your app’s domain.

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:

# Let's Encrypt
0 issue "letsencrypt.org"
0 issuewild "letsencrypt.org"

# Google Trust Services
0 issue "pki.goog; cansignhttpexchanges=yes"
0 issuewild "pki.goog; cansignhttpexchanges=yes"

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:

tsx
// Replace this array with an array of paths for pages in your app that do not require the
// user to be authenticated, e.g. a login page
const UNAUTHENTICATED_PAGES = [];

export const config = {
  // necessary to ensure that you are redirected to the refresh page
  matcher: '/',
};

export async function middleware(req: NextRequest) {
  const cookieAuthToken = req.cookies.get('privy-token');
  const cookieSession = req.cookies.get('privy-session');

  // Bypass middleware when `privy_oauth_code` is a query parameter, as
  // we are in the middle of an authentication flow
  if (req.nextUrl.searchParams.get('privy_oauth_code')) return NextResponse.next();

  // Bypass middleware when the /refresh page is fetched, otherwise
  // we will enter an infinite loop
  if (req.url.includes('/refresh')) return NextResponse.next();

  // If the user has `privy-token`, they are definitely authenticated
  const definitelyAuthenticated = Boolean(cookieAuthToken);
  // If user has `privy-session`, they also have `privy-refresh-token` and
  // may be authenticated once their session is refreshed in the client
  const maybeAuthenticated = Boolean(cookieSession);

  if (!definitelyAuthenticated && maybeAuthenticated) {
    // If user is not authenticated, but is maybe authenticated
    // redirect them to the `/refresh` page to trigger client-side refresh flow
    return NextResponse.redirect(new URL('/refresh', req.url));
  }

  return NextResponse.next();
}

INFO

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.