Pages Plugins
Put a staff area behind Cloudflare Access and check a signup form with Turnstile on your Pages site. Each plugin returns a Pages Function, which an F# module exports under a handler name such as onRequest.
- Libraries
Runtime.PagesPluginCloudflareAccessRuntime.PagesPluginTurnstileRuntime.PagesPluginStaticFormsRuntime.Workers - npm
@cloudflare/pages-plugin-cloudflare-access1.0.5,@cloudflare/pages-plugin-turnstile1.0.2,@cloudflare/pages-plugin-static-forms1.0.3 - Free plan unlimited static asset requests, and 100,000 Functions and Workers requests a day
Staff Area
Compiled to functions/staff/_middleware.js, this module is the middleware for /staff and every path below it. It validates the Access token in each request's Cf-Access-Jwt-Assertion header. When validation fails, it redirects the browser to your team's Access login page with a 302 status. aud is your Access application's audience tag, and domain is your team domain.
module Access = FSharp.CloudEdge.Runtime.PagesPluginCloudflareAccess
let onRequest =
Access.Exports.pagesPluginCloudflareAccess(
Access.PluginArgs.Create(
aud = "your-application-aud-tag",
domain = "https://your-team.cloudflareaccess.com"
)
)
Needs a Cloudflare Access application that covers /staff. Use its audience tag as aud.
Emitted JavaScript
import pages_plugin_cloudflare_access from "@cloudflare/pages-plugin-cloudflare-access";
export const onRequest = pages_plugin_cloudflare_access({
aud: "your-application-aud-tag",
domain: "https://your-team.cloudflareaccess.com",
});
Staff Profile
After the middleware, context.data.cloudflareAccess.JWT holds the token's payload and a getIdentity function. getIdentity requests the user's identity from your team domain with the token. It resolves to Some identity when the reply has a success status, and to None for any other status. The identity includes the user's name and email, and its groups array lists the user's groups.
open Fable.Core
module Workers = FSharp.CloudEdge.Runtime.Workers
module Access = FSharp.CloudEdge.Runtime.PagesPluginCloudflareAccess
let onRequestGet (context: Workers.EventContext<obj, string, Access.PluginData>) =
async {
let! identity = context.data.cloudflareAccess.JWT.getIdentity () |> Async.AwaitPromise
match identity with
| Some person ->
let profile = {| name = person.name; email = person.email; groups = person.groups |}
return Workers.Exports.Response.json profile
| None ->
let notFound = Workers.ResponseInit.Create(status = 404.)
return Workers.Exports.Response.Create("No identity for this sign-in", notFound)
}
|> Async.StartAsPromise
Needs the Staff Area middleware in the same directory or a parent directory.
Sign-out Link
generateLogoutURL builds the Access logout address for your team domain. The function responds with a 302 redirect to that address.
module Workers = FSharp.CloudEdge.Runtime.Workers
module Access = FSharp.CloudEdge.Runtime.PagesPluginCloudflareAccess
let onRequestGet (_: Workers.EventContext<obj, string, obj>) =
let logout =
Access.Api.Exports.generateLogoutURL(
Access.Api.GenerateLogoutURL0.Create(domain = "https://your-team.cloudflareaccess.com"))
Workers.Exports.Response.redirect(logout, 302.)
Emitted JavaScript
import { generateLogoutURL } from "@cloudflare/pages-plugin-cloudflare-access/api";
export function onRequestGet(_arg) {
const logout = generateLogoutURL({
domain: "https://your-team.cloudflareaccess.com",
});
return globalThis.Response.redirect(logout, 302);
}
Signup Form
Pages calls the functions of an exported array in order. The Turnstile plugin validates the token in the form's cf-turnstile-response field. When the token is valid, register handles the request next. For an invalid token, the plugin returns a 400 response with Turnstile's error descriptions.
open Fable.Core
module Workers = FSharp.CloudEdge.Runtime.Workers
module Turnstile = FSharp.CloudEdge.Runtime.PagesPluginTurnstile
let verifyHuman =
Turnstile.Exports.pagesPluginTurnstile(
Turnstile.PluginArgs.Create(secret = "1x0000000000000000000000000000000AA"))
let register (context: Workers.EventContext<obj, string, Turnstile.PluginData>) =
async {
let! form = context.request.formData () |> Async.AwaitPromise
match form.get "email" with
| Some (U2.Case1 email) -> return Workers.Exports.Response.Create($"Thanks for signing up, {email}.")
| _ ->
let badRequest = Workers.ResponseInit.Create(status = 400.)
return Workers.Exports.Response.Create("An email address is required.", badRequest)
}
|> Async.StartAsPromise
let onRequestPost: obj[] = [| verifyHuman; register |]
The type of a form value is U2<string, File>, and U2.Case1 matches the text of the email field.
Needs a Turnstile widget on the signup form. The secret in the code is Turnstile's test secret key, which passes every validation. A live site uses the widget's own secret key.
Library Table
Runtime.PagesPluginStaticForms binds the plugin that handles submissions from HTML forms with a data-static-form-name attribute. From .NET, Management.Security creates Access applications with AccessApplicationsAddAnApplication and Turnstile widgets with AccountsTurnstileWidgetCreate.
| Library | npm package | What it covers |
|---|---|---|
Runtime.PagesPluginCloudflareAccess |
@cloudflare/pages-plugin-cloudflare-access 1.0.5 |
Access middleware, identity |
Runtime.PagesPluginTurnstile |
@cloudflare/pages-plugin-turnstile 1.0.2 |
Turnstile validation |
Runtime.PagesPluginStaticForms |
@cloudflare/pages-plugin-static-forms 1.0.3 |
Static form replies |
Runtime.Workers |
@cloudflare/workers-types 5.20260906.1 |
Pages Functions context |
Related Pages
NuGet packages
Management.Security 0.1.0, Runtime.PagesPluginCloudflareAccess 0.1.0, Runtime.PagesPluginStaticForms 0.1.0, Runtime.PagesPluginTurnstile 0.1.0, Runtime.Workers 0.1.0.