Using http://ory.sh with Cloudflare

I’ve used some of my Christmas break to deep-dive into the many advancements the serverless & infrastructure world has made in the past year. I’ve been thoroughly enjoying my hands-on experience with Cloudflare Workers, Ory.sh, and PlanetScale, and I'm impressed by the level of innovation that is emerging from these platforms.

One issue I ran into with Cloudflare Workers was library support. As Workers is built on V8, not Node.js, some NPM modules are not supported. One of the major libraries that is not supported is Axios, with far-reaching implications: libraries that use Axios, such as @ory/client, will not run properly in Cloudflare Workers.

When trying to use one of these libraries, you will receive an error that looks like this:

TypeError: adapter is not a function
    at dispatchRequest (worker.js:873:16)
    at Axios.request (worker.js:1229:21)
    at Function.wrap [as request] (worker.js:63:21)
    at worker.js:1523:24
    at worker.js:2895:156
    at async authenticate (worker.js:6714:20)
    at async Object.handle (worker.js:6401:20) {
  stack: TypeError: adapter is not a function
    at dispat…0)
    at async Object.handle (worker.js:6401:20),
  message: adapter is not a function
}

After some wrangling, I found a solution. @haverstack/axios-fetch-adapter implements an Axios adapter that uses Fetch APIs instead of the Node-dependent default adapter of Axios. By using the baseOptions section of Ory's configuration, this adapter can be passed through to Ory API requests. This trick should also work for any other Axios-based modules that have Axios configuration options exposed within their module. Here’s how to get it working:

Firstly, in your wrangler.toml file, create your ORY_SDK_URL. This should be the URL given to you by the Ory application.

Next, create a new piece of middleware. We’re using itty-router, so our request middleware looks something like this:

// ./src/middleware/ory.ts

import { RequestWithMiddleware } from "../types/RequestWithMiddleware";

import * as oryApi from '@ory/client';
import { json } from 'itty-router-extras';
import { Session } from "@ory/client";
import fetchAdapter from "@haverstack/axios-fetch-adapter";

// get our globals from the Cloudflare environment
declare global {
    const ORY_SDK_URL: string;
}

// create our oryApi client
const ory = new oryApi.FrontendApi(
    new oryApi.Configuration({
        basePath: ORY_SDK_URL,
        baseOptions: {
            adapter: fetchAdapter
        }
    })
)

export default async function authenticate(request: RequestWithMiddleware) {
    // get our cookies from the header
    const cookies = request.headers.get("Cookie");

    try {
        const resp = await ory.toSession({ cookie: cookies });
        request.session = resp.data;
    } catch(e) {
        return json({ error: 'Not Authorized' }, { status: 401 })
    }
}

For completeness of this example, RequestWithMiddleware is an extension of itty-router’s default Request type. My RequestWithMiddleware class looks as follows…

// ./src/types/RequestWithMiddleWare.ts

import { Session } from "@ory/client";
import { Connection } from "@planetscale/database/dist";
import { RequestLike } from "itty-router";

export interface RequestWithMiddleware extends RequestLike {
    db: Connection; // the DB connection
    session: Session; // the ory session, if authenticated
}

…and the middleware is loaded in my main handler like so:

// ./src/handler.ts

(async () => {
  // ... config ...

	const router = Router();
	router.get("*", authenticate);
	// ... the rest of my routes
});

Now, any Ory sessions passed through to your Worker functions via the cookies will correctly be checked for authentication.