Validating JWT Tokens

Cloudflare Access generated JWT tokens are available in response header as Cf-Access-Jwt-Assertion and cookie as CF_Authorization. If you are not using Argo Tunnel, the JWT token should be validated by your application to verify the authenticity of these tokens and secure your origin.

Cloudflare uses RS256 to sign the JWT token using public private key pair. RS256 follows an asymmetric algorithm which means a private key is used to sign the JWT tokens and a separate public key is used to verify the signature. Upon configuring Access, the public certificates should be available under https://<Your Authentication Domain>/cdn-cgi/access/certs. If your application url is then your certificate URL would be

Manual Verification


Install lokey Install jq

  1. Run this command in your terminal after installing the prerequsites.

    $ curl -s https://<your auth domain>/cdn-cgi/access/certs | jq .keys[0] | lokey to pem
    -----BEGIN PUBLIC KEY-----
    -----END PUBLIC KEY-----
    If you get an error while running lokey, try again after installing python six library. pip install six==1.10.0

  2. Go to

  3. Switch the algorithm to RS256

  4. Paste your JWT token into the block on the left

  5. Enter the public key above in the public key box on the right

  6. Make sure the signature says verified

Programmatic Verification


Application AUD: This can be obtained from Cloudflare dashboard by navigating to the Access tab and click on the settings button on your application’s access policy as shown below. aud-tag Certificate URL: https://<Your Authentication Domain>/cdn-cgi/access/certs

JWT Issuer: https://<Your Authentication Domain>

Golang Example

package main
import (
var (
    ctx        = context.TODO()
    authDomain = ""
    certsURL   = fmt.Sprintf("%s/cdn-cgi/access/certs", authDomain)
    // policyAUD is your application AUD value
    policyAUD = "4714c1358e65fe4b408ad6d432a5f878f08194bdb4752441fd56faefa9b2b6f2"
    config = &oidc.Config{
        ClientID: policyAUD,
    keySet   = oidc.NewRemoteKeySet(ctx, certsURL)
    verifier = oidc.NewVerifier(authDomain, keySet, config)
// VerifyToken is a middleware to verify a CF Access token
func VerifyToken(next http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        headers := r.Header

        // Make sure that the incoming request has our token header
        //  Could also look in the cookies for CF_AUTHORIZATION
        accessJWT := headers.Get("Cf-Access-Jwt-Assertion")
        if accessJWT == "" {
            w.Write([]byte("No token on the request"))
        // Verify the access token
        ctx := r.Context()
        _, err := verifier.Verify(ctx, accessJWT)
        if err != nil {
            w.Write([]byte(fmt.Sprintf("Invalid token: %s", err.Error())))
        next.ServeHTTP(w, r)
    return http.HandlerFunc(fn)
func MainHandler() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
func main() {
    http.Handle("/", VerifyToken(MainHandler()))
    http.ListenAndServe(":3000", nil)

Python Example

You need to pip install the following:

  • flask
  • requests
  • PyJWT

from flask import Flask, request
import requests
import jwt
import json
import os
app = Flask(__name__)
# Your policies audience tag
POLICY_AUD = os.getenv("POLICY_AUD")
# Your CF Access Authentication domain
CERTS_URL = "{}/cdn-cgi/access/certs".format(AUTH_DOMAIN)
def _get_public_keys():
        List of RSA public keys usable by PyJWT.
    r = requests.get(CERTS_URL)
    public_keys = []
    jwk_set = r.json()
    for key_dict in jwk_set['keys']:
        public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict))
    return public_keys
def verify_token(f):
    Decorator that wraps a Flask API call to verify the CF Access JWT
    def wrapper():
        token = ''
        if 'CF_Authorization' in request.cookies:
            token = request.cookies['CF_Authorization']
            return "missing required cf authorization token", 403
        keys = _get_public_keys()
        # Loop through the keys since we can't pass the key set to the decoder
        valid_token = False
        for key in keys:
                # decode returns the claims which has the email if you need it
                jwt.decode(token, key=key, audience=POLICY_AUD)
                valid_token = True
        if not valid_token:
            return "invalid token", 403
        return f()
    return wrapper
def hello_world():
    return 'Hello, World!'
if __name__ == '__main__':