← posts / ai integrations

Strict service token auth in Cloudflare Access: moving your scripts and CI over before it bites

Cloudflare Access now has a strict mode for service tokens: 401/403 instead of a 302 to the login page, only Service Auth policies count, and no CF_Authorization cookie. New orgs get it forced on from 5 October. A 15-minute check and switch for existing orgs.

if.codesOct 6, 2026 · 5 min read#cloudflare#zero-trust#ci-cd#authenticationAI-assisted

On 2 October Cloudflare published a changelog entry for a new Zero Trust setting, strict service token authentication. If your scripts, cron jobs or CI runners reach an Access-protected app with a service token, this setting changes how failures look and which policies apply. Organizations created on or after 5 October 2026 get it switched on and cannot turn it off. Existing organizations can choose when to switch it on.

This guide shows what changes, how to find the callers that will break, and how to switch it on and check the result. It takes about 15 minutes for a small account. Everything below comes from Cloudflare's changelog and service-token docs. We have not run our own benchmarks here.

What strict mode changes

The docs list three behaviour changes:

Strict mode replaces redirects with explicit error codes.
Strict mode replaces redirects with explicit error codes.
  • Failures are explicit. If authentication or authorization fails, Access always returns 401 or 403 instead of redirecting the client to the login page with a 302.
  • Only Service Auth policies count. Only policies with the Service Auth action can authorize the request. Access ignores Allow policies and any CF_Authorization cookie sent with the request.
  • No cookie on success. Access does not return a CF_Authorization cookie to the client after it authenticates successfully.

Cloudflare also says that failed requests for recognised service tokens show up in the Access authentication logs, including expired tokens, disabled tokens and wrong client secrets.

The first change is the one that helps. Without strict mode, a script with a bad token often gets a 302 to an HTML login page. If the script follows redirects, it then gets a 200 for the login page, and only fails later when it tries to parse that page as JSON. With strict mode it gets a clean 401 or 403 on the first request.

Who breaks

Three patterns stop working when you turn strict mode on:

Ensure your CI runners and scripts are configured for the new authentication flow.
Ensure your CI runners and scripts are configured for the new authentication flow.
  1. Service tokens matched by an Allow policy. If a policy includes a service token rule but its action is Allow, the token no longer authorizes anything. The policy action has to be Service Auth.
  2. Clients that send the headers once and then rely on the cookie. Some scripts authenticate on the first call, save cookies with a cookie jar, and drop the headers on later calls. With no cookie issued (and any cookie ignored), every request after the first gets a 401 or 403.
  3. Anything that expects the redirect. Health checks or wrappers that treat 302 as "not logged in" and branch on it will see a different status code.

Step 1: find Allow policies that contain service tokens

In the dashboard, open each Access application that machines call and check its policies. Any policy that includes a service token (or "any valid service token") should have the Service Auth action. In the API, the Service Auth action appears as "decision": "non_identity".

To check reusable policies from the command line, use an API token with Access read permissions:

export ACCOUNT_ID="your-account-id"
export CLOUDFLARE_API_TOKEN="your-api-token"

curl -s "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/policies" \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  | jq '.result[]
        | select(.decision == "allow")
        | select([.include[]? | has("service_token") or has("any_valid_service_token")] | any)
        | {id, name, decision}'

Every policy this prints is a policy that strict mode will ignore for your tokens. Change its action to Service Auth in the dashboard, or split it into two policies: one Allow policy for people and one Service Auth policy for tokens. Policies attached directly to a single application are listed under that application in the dashboard. Check those too.

Step 2: make every caller send both headers on every request

A service token is two headers. Per the docs:

CF-Access-Client-Id: <CLIENT_ID>
CF-Access-Client-Secret: <CLIENT_SECRET>

The docs also describe a single-header form for clients that can only set Authorization:

Authorization: {"cf-access-client-id": "<CLIENT_ID>", "cf-access-client-secret": "<CLIENT_SECRET>"}

Search your repos and CI config for cookie jars and saved sessions next to Access hostnames, for example curl -c/-b, requests.Session() that only sets the headers on login, or a stored CF_Authorization value. Change them so the two headers go on every request.

Before you flip the setting, run this check against each protected endpoint. It does not follow redirects, so a 302 stays visible:

#!/usr/bin/env bash
# check-access.sh: call an Access-protected URL with a service token, no redirects.
set -euo pipefail

URL="${1:?usage: check-access.sh https://app.example.com/api/health}"

curl -s -o /dev/null \
  -w "status=%{http_code} redirect=%{redirect_url}\n" \
  --header "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
  --header "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
  "$URL"
export CF_ACCESS_CLIENT_ID="xxxx.access"
export CF_ACCESS_CLIENT_SECRET="xxxx"
bash check-access.sh https://app.example.com/api/health

You want status=200 (or whatever your origin normally returns) with an empty redirect. A 302 to a login page before the switch means the token is not being matched by any policy that admits it.

Want AI wired into the systems you already run?I build LLM integrations with costs and quality you can see. The estimate is free.

Step 3: switch it on

In the dashboard: Zero Trust > Access controls > Access settings, then under Manage service tokens turn on Strict service token authentication and confirm.

Activating strict service token authentication in the dashboard.
Activating strict service token authentication in the dashboard.

Or with the API, as given in the changelog:

curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/access/organizations" \
  --request PATCH \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --json '{"strict_service_token_auth": true}'

To roll back on an existing organization, send the same request with false. Organizations created on or after 5 October 2026 cannot roll back, so any new account you set up for a client or a side project starts in strict mode.

Step 4: rerun the checks and read the logs

Run check-access.sh again for each endpoint, then run your real jobs once. Expected results:

  • 200: the token matched a Service Auth policy. Done.
  • 401: the token is missing, expired, disabled, or has a wrong secret.
  • 403: the token is valid but no Service Auth policy on that application admits it. This is usually an Allow policy you missed in step 1.

For the failures, open the Access authentication logs in the Zero Trust dashboard. Because recognised tokens now log their failures, you can see which token failed and why. That is quicker than guessing from a 302.

Things that do not change

  • People who sign in through your identity provider still use Allow policies and still get the cookie. Strict mode only changes how service-token requests are handled.
  • Tokens still expire on the schedule you chose when you created them. The docs say the client secret is shown only once, so a lost secret means a new token.
  • Creating tokens works as before: Zero Trust > Access controls > Service credentials > Service Tokens, or POST /accounts/$ACCOUNT_ID/access/service_tokens with the Access: Service Tokens Write permission.

When to do it

If your organization is older than 5 October, nothing forces the switch today. Still, a migration you choose is easier than finding out during an incident that a nightly job has been parsing a login page. Run steps 1 and 2 this week, switch on strict mode during a quiet hour, and keep the PATCH with false ready in case you need to roll back.

Sources

if.codesI build RAG, AI integrations and agent pipelines on Go and Python backends — and write about it here.
// keep reading

More posts on AI and backends.

// free quote

Read something you need? I’ll quote it for free.

RAG, AI integrations, agents or the backend underneath — tell me what you have and what should change. I read every request myself.

Free · no commitment

Tell me what you have. I’ll tell you what it takes.

1Describe the projectA few sentences is enough — about two minutes.
2I review itI read it myself and may ask a follow-up question.
3You get a free quoteScope, approach and estimate — yours to keep, no strings.
What kind of project is it?
Free and without obligation. Your details are used only to reply — see the privacy policy.