npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hyvmind/authkit-remix-cloudflare

v0.17.0

Published

Cloudflare-compatible auth and session helpers for using WorkOS & AuthKit with Remix

Readme

AuthKit Remix Library (Cloudflare Workers/Pages)

The AuthKit library for Remix on Cloudflare provides convenient helpers for authentication and session management using WorkOS & AuthKit with Remix, purpose-built for Cloudflare Workers and Pages.

Note: This is a Cloudflare-compatible fork of @workos-inc/authkit-remix, matching upstream v0.17.0. The upstream library relies on process.env and Node.js APIs unavailable in Cloudflare Workers. This fork replaces those with Cloudflare-compatible alternatives using context.cloudflare.env and a centralized configuration system.

Installation

Install the package with:

npm i @hyvmind/authkit-remix-cloudflare

or

yarn add @hyvmind/authkit-remix-cloudflare

Configuration

Cloudflare Workers don't have a global process.env. Environment variables are delivered per-request through the loader's context.cloudflare.env. This library provides configureFromContext() to bridge that gap.

1. Environment Variables (.dev.vars)

Add your WorkOS credentials to a .dev.vars file in your project root (this is Cloudflare's equivalent of .env):

WORKOS_CLIENT_ID="client_..."              # retrieved from the WorkOS dashboard
WORKOS_API_KEY="sk_test_..."               # retrieved from the WorkOS dashboard
WORKOS_REDIRECT_URI="http://localhost:5173/callback" # configured in the WorkOS dashboard
WORKOS_COOKIE_PASSWORD="<your password>"   # generate a secure password here

WORKOS_COOKIE_PASSWORD is the private key used to encrypt the session cookie. It must be at least 32 characters long. You can generate one with:

openssl rand -base64 24

To use the signOut method, you'll need to set your app's homepage in your WorkOS dashboard settings under "Redirects".

2. Initialize from Context

The recommended approach is to call configureFromContext() in your root loader. This reads the WORKOS_* variables from context.cloudflare.env and makes them available to all AuthKit functions:

import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
import { configureFromContext, authkitLoader } from '@hyvmind/authkit-remix-cloudflare';

export const loader = (args: LoaderFunctionArgs) => {
  configureFromContext(args.context);
  return authkitLoader(args);
};

3. Programmatic Configuration

You can also configure AuthKit programmatically using the configure function:

import { configure } from '@hyvmind/authkit-remix-cloudflare';

// Pass a config object with explicit values
configure({
  clientId: 'client_1234567890',
  apiKey: 'sk_test_1234567890',
  redirectUri: 'http://localhost:5173/callback',
  cookiePassword: 'your-secure-cookie-password-min-32-chars',
});

Or provide a custom value source function:

// Use a function to resolve environment variable names
configure((key) => context.cloudflare.env[key]);

Or combine both — explicit config values with a fallback source:

configure({ cookieName: 'my-custom-session' }, context.cloudflare.env);

Configuration Priority

When retrieving configuration values, AuthKit follows this priority order:

  1. Environment variables (via the value source set by configure() / configureFromContext())
  2. Programmatically provided values
  3. Default values for optional settings

Available Configuration Options

| Option | Environment Variable | Default | Required | Description | | ---------------- | ------------------------ | --------------------- | -------- | --------------------------------------------- | | clientId | WORKOS_CLIENT_ID | – | Yes | Your WorkOS Client ID | | apiKey | WORKOS_API_KEY | – | Yes | Your WorkOS API Key | | redirectUri | WORKOS_REDIRECT_URI | – | Yes | The callback URL configured in WorkOS | | cookiePassword | WORKOS_COOKIE_PASSWORD | – | Yes | Password for cookie encryption (min 32 chars) | | cookieName | WORKOS_COOKIE_NAME | wos-session | No | Name of the session cookie | | apiHttps | WORKOS_API_HTTPS | true | No | Whether to use HTTPS for API calls | | cookieMaxAge | WORKOS_COOKIE_MAX_AGE | 34560000 (400 days) | No | Maximum age of cookie in seconds | | apiHostname | WORKOS_API_HOSTNAME | api.workos.com | No | WorkOS API hostname | | apiPort | WORKOS_API_PORT | – | No | Port to use for API calls |

Optional Environment Variables

These can also be set in .dev.vars for debugging or customization:

WORKOS_COOKIE_MAX_AGE='600'          # maximum age of the cookie in seconds
WORKOS_API_HOSTNAME='api.workos.com' # base WorkOS API URL
WORKOS_API_HTTPS=true                # whether to use HTTPS in API calls
WORKOS_API_PORT=3000                 # port to use for API calls

Setup

Callback Route

AuthKit requires a callback URL to redirect users back to after authentication. In your Remix app, create a new route and add the following:

import { authLoader } from '@hyvmind/authkit-remix-cloudflare';

export const loader = authLoader();

Make sure this route matches the WORKOS_REDIRECT_URI variable and the configured redirect URI in your WorkOS dashboard. For instance, if your redirect URI is http://localhost:5173/callback then you'd put the above code in /app/routes/callback.ts.

You can control the pathname the user will be sent to after signing in by passing a returnPathname option:

export const loader = authLoader({ returnPathname: '/dashboard' });

If your application needs to persist oauthTokens or other auth-related information after a successful callback, you can pass an onSuccess option:

export const loader = authLoader({
  onSuccess: async ({ accessToken, refreshToken, user, oauthTokens, organizationId, impersonator }) => {
    await saveToDatabase(oauthTokens);
  },
});

Root Loader

Initialize AuthKit in your root loader so the session is available across your entire application:

import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
import { configureFromContext, authkitLoader } from '@hyvmind/authkit-remix-cloudflare';

export const loader = (args: LoaderFunctionArgs) => {
  configureFromContext(args.context);
  return authkitLoader(args);
};

Usage

Access Authentication Data

Use authkitLoader in any route to access authentication data. The returned data includes user, sessionId, organizationId, role, roles, permissions, entitlements, and impersonator.

import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
import { useLoaderData } from '@remix-run/react';
import { authkitLoader } from '@hyvmind/authkit-remix-cloudflare';

export const loader = (args: LoaderFunctionArgs) => authkitLoader(args);

export default function App() {
  const { user } = useLoaderData<typeof loader>();

  return (
    <div>
      <p>Welcome back{user?.firstName && `, ${user.firstName}`}</p>
    </div>
  );
}

Custom Loader Functions

Pass a loader function to authkitLoader to combine auth data with your own data. The function receives the standard Remix loader args plus auth and getAccessToken:

import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
import { Form, Link, useLoaderData } from '@remix-run/react';
import { getSignInUrl, getSignUpUrl, signOut, authkitLoader } from '@hyvmind/authkit-remix-cloudflare';

export const loader = (args: LoaderFunctionArgs) =>
  authkitLoader(args, async ({ request, auth }) => {
    return {
      signInUrl: await getSignInUrl(),
      signUpUrl: await getSignUpUrl(),
    };
  });

export async function action({ request }: ActionFunctionArgs) {
  return await signOut(request);
}

export default function HomePage() {
  const { user, signInUrl, signUpUrl } = useLoaderData<typeof loader>();

  if (!user) {
    return (
      <>
        <Link to={signInUrl}>Log in</Link>
        <br />
        <Link to={signUpUrl}>Sign Up</Link>
      </>
    );
  }

  return (
    <Form method="post">
      <p>Welcome back{user?.firstName && `, ${user.firstName}`}</p>
      <button type="submit">Sign out</button>
    </Form>
  );
}

Requiring Auth

For pages where a signed-in user is mandatory, use the ensureSignedIn option:

export const loader = (args: LoaderFunctionArgs) => authkitLoader(args, { ensureSignedIn: true });

Enabling ensureSignedIn will redirect unauthenticated users to AuthKit automatically. When used with a custom loader, auth.user is guaranteed to be non-null:

export const loader = (args: LoaderFunctionArgs) =>
  authkitLoader(
    args,
    async ({ auth, getAccessToken }) => {
      // auth.user is guaranteed to exist
      const accessToken = getAccessToken(); // always returns a string
      return { profile: await fetchProfile(accessToken) };
    },
    { ensureSignedIn: true },
  );

Getting the Access Token

Access tokens are available through the getAccessToken() callback within your loader function. By design, access tokens are not included in the default response data — this prevents accidental exposure in browser developer tools, HTML source, or client-side logs.

import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
import { authkitLoader } from '@hyvmind/authkit-remix-cloudflare';

export const loader = (args: LoaderFunctionArgs) =>
  authkitLoader(args, async ({ auth, getAccessToken }) => {
    if (!auth.user) {
      return { data: null };
    }

    const accessToken = getAccessToken();

    const serviceData = await fetch('https://api.example.com/data', {
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    });

    return {
      data: await serviceData.json(),
    };
  });

Security note: Prefer making API calls server-side in your loaders rather than exposing access tokens to the client. If you must expose the token to client-side code, explicitly return it from your loader and consider using tokens with limited scope.

Using withAuth for Lightweight Auth Checks

For advanced use cases, the withAuth function provides direct access to authentication data without the full loader machinery. Unlike authkitLoader, it:

  • Does not handle automatic token refresh
  • Does not manage cookies or session updates
  • Returns the accessToken directly as a property
  • Requires manual redirect handling for unauthenticated users

Important: withAuth requires that authkitLoader is used in a parent or root route to handle session refresh and cookie management.

import type { LoaderFunctionArgs } from '@remix-run/cloudflare';
import { redirect } from '@remix-run/cloudflare';
import { withAuth } from '@hyvmind/authkit-remix-cloudflare';

export const loader = async (args: LoaderFunctionArgs) => {
  const auth = await withAuth(args);

  if (!auth.user) {
    throw redirect('/sign-in');
  }

  const { accessToken, user, sessionId, organizationId, role, roles, permissions, entitlements } = auth;

  const apiData = await fetch('https://api.example.com/data', {
    headers: { Authorization: `Bearer ${accessToken}` },
  });

  return {
    user,
    apiData: await apiData.json(),
  };
};

When to use withAuth vs authkitLoader:

  • Use authkitLoader for most cases — it handles token refresh, cookies, and provides safer defaults
  • Use withAuth when you need more control or are building custom authentication flows (e.g., API routes)

Organization Switching

Use switchToOrganization to switch the current session to a different organization:

import type { ActionFunctionArgs } from '@remix-run/cloudflare';
import { switchToOrganization } from '@hyvmind/authkit-remix-cloudflare';

export async function action({ request }: ActionFunctionArgs) {
  return await switchToOrganization(request, 'org_01234567890', {
    returnTo: '/dashboard', // optional redirect after switching
  });
}

If returnTo is provided, the user is redirected to that path with the updated session cookie. Otherwise, a JSON response with the updated auth data is returned.

Signing Out

Use the signOut method to sign out the current user, end the session, and redirect to your app's homepage. The homepage redirect is set in your WorkOS dashboard settings under "Redirects".

import type { ActionFunctionArgs } from '@remix-run/cloudflare';
import { signOut } from '@hyvmind/authkit-remix-cloudflare';

export async function action({ request }: ActionFunctionArgs) {
  return await signOut(request);
}

To specify where the user is redirected after sign-out, pass an optional returnTo argument. Allowed values are configured in the WorkOS Dashboard under Logout redirects:

export async function action({ request }: ActionFunctionArgs) {
  return await signOut(request, { returnTo: 'https://example.com' });
}

Debugging

To enable debug logs, pass in the debug flag:

export const loader = (args: LoaderFunctionArgs) => authkitLoader(args, { debug: true });

If providing a loader function, pass the options object as the third parameter:

export const loader = (args: LoaderFunctionArgs) =>
  authkitLoader(
    args,
    async ({ auth }) => {
      return { foo: 'bar' };
    },
    { debug: true },
  );

Custom Session Storage

By default, AuthKit uses cookie-based session storage with these settings:

{
  name: "wos-session",
  path: "/",
  httpOnly: true,
  secure: true,       // when redirect URI uses HTTPS
  sameSite: "lax",
  maxAge: 34560000,    // 400 days (configurable via WORKOS_COOKIE_MAX_AGE)
  secrets: [/* WORKOS_COOKIE_PASSWORD */],
}

You can provide your own session storage implementation to both authkitLoader and authLoader:

import { createCookieSessionStorage } from '@remix-run/cloudflare';
import { authkitLoader, authLoader } from '@hyvmind/authkit-remix-cloudflare';

const customStorage = createCookieSessionStorage({
  cookie: {
    name: 'my-auth-session',
    secrets: ['my-secret'],
    sameSite: 'lax',
    path: '/',
    httpOnly: true,
    secure: true,
    maxAge: 60 * 60 * 24, // 1 day
  },
});

// In your root loader
export const loader = (args: LoaderFunctionArgs) =>
  authkitLoader(args, {
    storage: customStorage,
    cookie: { name: 'my-auth-session' },
  });

// In your callback route
export const loader = authLoader({
  storage: customStorage,
  cookie: { name: 'my-auth-session' },
});

For code reuse and consistency, consider a shared helper:

// app/lib/session.server.ts
import { createCookieSessionStorage } from '@remix-run/cloudflare';

export function getAuthStorage() {
  const storage = createCookieSessionStorage({
    cookie: {
      name: 'my-session',
      secrets: ['my-secret'],
      sameSite: 'lax',
      path: '/',
      httpOnly: true,
      secure: true,
    },
  });
  return { storage, cookie: { name: 'my-session' } };
}

// Then in your routes
import { getAuthStorage } from '~/lib/session.server';

export const loader = (args: LoaderFunctionArgs) =>
  authkitLoader(args, {
    ...getAuthStorage(),
  });

AuthKit works with any session storage that implements Remix's SessionStorage interface.

Session Refresh Callbacks

You can hook into the session refresh lifecycle for logging, analytics, or custom error handling:

export const loader = (args: LoaderFunctionArgs) =>
  authkitLoader(args, {
    onSessionRefreshSuccess: async ({ accessToken, user, impersonator, organizationId }) => {
      console.log('Session refreshed for user:', user.id);
    },
    onSessionRefreshError: async ({ error, request, sessionData }) => {
      console.error('Session refresh failed:', error);
      // Optionally return a Response to override the default redirect behavior
      // return redirect('/custom-error-page');
    },
  });
  • onSessionRefreshSuccess is called after a successful token refresh with the new accessToken, user, impersonator, and organizationId.
  • onSessionRefreshError is called when a token refresh fails. If it returns a Response, that response is used instead of the default redirect to the sign-in page.

Migration from v0.0.x

If you're upgrading from the earlier 0.0.x versions of this Cloudflare fork, here are the breaking changes:

configureFromContext() is now required

Previously, functions accepted context as a parameter. Now you must call configureFromContext(context) once (typically in your root loader) before using any AuthKit functions:

// Before (v0.0.x)
export const loader = (args: LoaderFunctionArgs) => authkitLoader(args);

// After (v0.17.x)
export const loader = (args: LoaderFunctionArgs) => {
  configureFromContext(args.context);
  return authkitLoader(args);
};

Access tokens removed from default response

Access tokens are no longer included in the default authkitLoader response data. Use the getAccessToken() callback instead:

// Before (v0.0.x) — accessToken was in the response
const { accessToken } = useLoaderData<typeof loader>();

// After (v0.17.x) — use getAccessToken() in the loader
export const loader = (args: LoaderFunctionArgs) =>
  authkitLoader(args, async ({ auth, getAccessToken }) => {
    const accessToken = getAccessToken();
    return { data: await fetchData(accessToken) };
  });

getWorkos() renamed to getWorkOS()

The function to access the WorkOS client instance has been renamed for consistency:

// Before
import { getWorkos } from '@hyvmind/authkit-remix-cloudflare';

// After
import { getWorkOS } from '@hyvmind/authkit-remix-cloudflare';

New fields: roles and entitlements

The auth data now includes roles (array of role strings) and entitlements (array of entitlement strings) alongside the existing role and permissions fields.

signOut signature simplified

signOut no longer requires the context parameter:

// Before (v0.0.x)
return await signOut(request, context);

// After (v0.17.x)
return await signOut(request);
return await signOut(request, { returnTo: 'https://example.com' });

API Reference

| Export | Type | Description | | ------------------------------------------------- | -------- | ----------------------------------------------------------------------------- | | configureFromContext(context) | Function | Initialize AuthKit from Remix's AppLoadContext (recommended for Cloudflare) | | configure(configOrSource, source?) | Function | Configure AuthKit with a config object, value source, or both | | getConfig(key) | Function | Retrieve a configuration value by key | | authkitLoader(args, loaderOrOptions?, options?) | Function | Authentication-aware route loader with session management | | authLoader(options?) | Function | Callback route loader for handling the OAuth redirect | | getSignInUrl(returnPathname?) | Function | Get the AuthKit sign-in URL | | getSignUpUrl(returnPathname?) | Function | Get the AuthKit sign-up URL | | signOut(request, options?) | Function | Sign out the user and redirect | | switchToOrganization(request, orgId, options?) | Function | Switch the session to a different organization | | withAuth(args) | Function | Lightweight auth check without session refresh | | getWorkOS() | Function | Get the lazily-initialized WorkOS client instance |

License

MIT