Browse Cloudflare Access-Protected Pages
Access pages protected by Cloudflare Access zero-trust policies by passing Service Token credentials or reusing a saved authenticated profile.
- A Browserless API token from your account dashboard
- Cloudflare Access Service Token credentials (
CF-Access-Client-IdandCF-Access-Client-Secret) or a valid Browserless authenticated profile for the protected site
Steps
There are two approaches depending on your Cloudflare Access configuration:
- Service Tokens — machine-to-machine credentials issued by your Cloudflare team. Inject them as HTTP headers on every request.
- User sessions — save a logged-in browser state to a Browserless profile and reuse it. See Save Logins to Authenticated Profiles.
- REST API
- Frameworks
Use the REST API with Cloudflare Access service token headers to reach protected pages.
- cURL
- JavaScript
- Python
- Java
- C#
- Go
- PHP
- Ruby
1. Use a saved authenticated profile
If you have a Browserless profile saved after logging in through Cloudflare Access, append profile= to any request:
curl -X POST \
"https://production-sfo.browserless.io/screenshot?token=YOUR_API_TOKEN_HERE&profile=cf-access-profile" \
-H "Content-Type: application/json" \
-d '{ "url": "https://internal.example.com/dashboard" }' \
--output dashboard.png
2. Inject Service Token headers
For machine-to-machine access, use the /content endpoint with setExtraHTTPHeaders to pass the token on every navigation:
curl -X POST \
"https://production-sfo.browserless.io/content?token=YOUR_API_TOKEN_HERE" \
-H "Content-Type: application/json" \
-d '{
"url": "https://internal.example.com/dashboard",
"setExtraHTTPHeaders": {
"CF-Access-Client-Id": "YOUR_CF_CLIENT_ID.access",
"CF-Access-Client-Secret": "YOUR_CF_CLIENT_SECRET"
}
}'
1. Install dependencies
npm install puppeteer-core
2. Inject Service Token headers
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE',
});
try {
const page = await browser.newPage();
// Headers must be set before navigation — each request to a CF Access-protected origin requires them.
await page.setExtraHTTPHeaders({
'CF-Access-Client-Id': 'YOUR_CF_CLIENT_ID.access',
'CF-Access-Client-Secret': 'YOUR_CF_CLIENT_SECRET',
});
await page.goto('https://internal.example.com/dashboard', {
waitUntil: 'networkidle2',
});
console.log('Title:', await page.title());
} finally {
// Always close to release the session even on error.
await browser.close();
}
3. Or reuse a saved profile
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint:
'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE&profile=cf-access-profile',
});
try {
const page = await browser.newPage();
await page.goto('https://internal.example.com/dashboard');
console.log('Title:', await page.title());
} finally {
// Always close to release the session even on error.
await browser.close();
}
1. Install dependencies
pip install playwright
2. Inject Service Token headers
from playwright.sync_api import sync_playwright
TOKEN = 'YOUR_API_TOKEN_HERE'
WS_ENDPOINT = f'wss://production-sfo.browserless.io/chromium/playwright?token={TOKEN}'
with sync_playwright() as playwright:
browser = playwright.chromium.connect(WS_ENDPOINT)
try:
context = browser.new_context(
extra_http_headers={
'CF-Access-Client-Id': 'YOUR_CF_CLIENT_ID.access',
'CF-Access-Client-Secret': 'YOUR_CF_CLIENT_SECRET',
}
)
page = context.new_page()
page.goto('https://internal.example.com/dashboard')
print('Title:', page.title())
finally:
# Always close to release the session even on error.
browser.close()
3. Or reuse a saved profile
from playwright.sync_api import sync_playwright
TOKEN = 'YOUR_API_TOKEN_HERE'
WS_ENDPOINT = f'wss://production-sfo.browserless.io?token={TOKEN}&profile=cf-access-profile'
with sync_playwright() as playwright:
# connect_over_cdp preserves the saved profile's context and cookies.
browser = playwright.chromium.connect_over_cdp(WS_ENDPOINT)
try:
context = browser.contexts[0]
page = context.pages[0]
page.goto('https://internal.example.com/dashboard')
print('Title:', page.title())
finally:
# Always close to release the session even on error.
browser.close()
1. Dependencies
java.net.http.HttpClient ships with the JDK. No extra packages needed. Use Jackson or Gson in production to serialize the nested setExtraHTTPHeaders object safely.
2. Use a saved authenticated profile
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
public class CloudflareAccess {
public static void main(String[] args) throws Exception {
String token = "YOUR_API_TOKEN_HERE";
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://production-sfo.browserless.io/screenshot"
+ "?token=" + token + "&profile=cf-access-profile"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"url\":\"https://internal.example.com/dashboard\"}"))
.build();
HttpResponse<byte[]> res = client.send(req, HttpResponse.BodyHandlers.ofByteArray());
Files.write(Path.of("dashboard.png"), res.body());
System.out.println("Saved dashboard.png");
}
}
3. Inject Service Token headers
import java.net.URI;
import java.net.http.*;
public class CloudflareAccessToken {
public static void main(String[] args) throws Exception {
String token = "YOUR_API_TOKEN_HERE";
HttpClient client = HttpClient.newHttpClient();
// Use Jackson or Gson for production — inline JSON is fragile for values with special characters.
String body = "{\"url\":\"https://internal.example.com/dashboard\","
+ "\"setExtraHTTPHeaders\":{"
+ "\"CF-Access-Client-Id\":\"YOUR_CF_CLIENT_ID.access\","
+ "\"CF-Access-Client-Secret\":\"YOUR_CF_CLIENT_SECRET\"}}";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://production-sfo.browserless.io/content?token=" + token))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body().substring(0, Math.min(200, res.body().length())));
}
}
1. Dependencies
System.Net.Http.HttpClient and System.Text.Json are part of the .NET standard library.
2. Use a saved authenticated profile
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class CloudflareAccess
{
static async Task Main()
{
const string token = "YOUR_API_TOKEN_HERE";
using var client = new HttpClient();
var body = new StringContent(
"{\"url\":\"https://internal.example.com/dashboard\"}",
Encoding.UTF8, "application/json");
var res = await client.PostAsync(
$"https://production-sfo.browserless.io/screenshot?token={token}&profile=cf-access-profile",
body);
await File.WriteAllBytesAsync("dashboard.png", await res.Content.ReadAsByteArrayAsync());
Console.WriteLine("Saved dashboard.png");
}
}
3. Inject Service Token headers
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class CloudflareAccessToken
{
static async Task Main()
{
const string token = "YOUR_API_TOKEN_HERE";
using var client = new HttpClient();
var payload = JsonSerializer.Serialize(new
{
url = "https://internal.example.com/dashboard",
setExtraHTTPHeaders = new Dictionary<string, string>
{
["CF-Access-Client-Id"] = "YOUR_CF_CLIENT_ID.access",
["CF-Access-Client-Secret"] = "YOUR_CF_CLIENT_SECRET",
}
});
var body = new StringContent(payload, Encoding.UTF8, "application/json");
var res = await client.PostAsync(
$"https://production-sfo.browserless.io/content?token={token}",
body);
string content = await res.Content.ReadAsStringAsync();
Console.WriteLine(content[..Math.Min(200, content.Length)]);
}
}
1. Dependencies
encoding/json and net/http are part of Go's standard library.
2. Use a saved authenticated profile
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
)
func main() {
token := "YOUR_API_TOKEN_HERE"
req, _ := http.NewRequest("POST",
"https://production-sfo.browserless.io/screenshot?token="+token+"&profile=cf-access-profile",
bytes.NewBufferString(`{"url":"https://internal.example.com/dashboard"}`),
)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer res.Body.Close()
data, _ := io.ReadAll(res.Body)
os.WriteFile("dashboard.png", data, 0644)
fmt.Println("Saved dashboard.png")
}
3. Inject Service Token headers
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
token := "YOUR_API_TOKEN_HERE"
payload, _ := json.Marshal(map[string]interface{}{
"url": "https://internal.example.com/dashboard",
"setExtraHTTPHeaders": map[string]string{
"CF-Access-Client-Id": "YOUR_CF_CLIENT_ID.access",
"CF-Access-Client-Secret": "YOUR_CF_CLIENT_SECRET",
},
})
req, _ := http.NewRequest("POST",
"https://production-sfo.browserless.io/content?token="+token,
bytes.NewBuffer(payload),
)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer res.Body.Close()
content, _ := io.ReadAll(res.Body)
end := len(content)
if end > 200 { end = 200 }
fmt.Println(string(content[:end]))
}
1. Dependencies
This example uses PHP's built-in curl. No Composer packages needed.
2. Use a saved authenticated profile
<?php
$token = 'YOUR_API_TOKEN_HERE';
$ch = curl_init("https://production-sfo.browserless.io/screenshot?token={$token}&profile=cf-access-profile");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['url' => 'https://internal.example.com/dashboard']),
CURLOPT_RETURNTRANSFER => true,
]);
$png = curl_exec($ch);
curl_close($ch);
file_put_contents('dashboard.png', $png);
echo "Saved dashboard.png\n";
3. Inject Service Token headers
<?php
$token = 'YOUR_API_TOKEN_HERE';
$ch = curl_init("https://production-sfo.browserless.io/content?token={$token}");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://internal.example.com/dashboard',
'setExtraHTTPHeaders' => [
'CF-Access-Client-Id' => 'YOUR_CF_CLIENT_ID.access',
'CF-Access-Client-Secret' => 'YOUR_CF_CLIENT_SECRET',
],
]),
CURLOPT_RETURNTRANSFER => true,
]);
$content = curl_exec($ch);
curl_close($ch);
echo substr($content, 0, 200) . "\n";
1. Dependencies
net/http and json are part of Ruby's standard library.
2. Use a saved authenticated profile
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_API_TOKEN_HERE'
uri = URI("https://production-sfo.browserless.io/screenshot?token=#{TOKEN}&profile=cf-access-profile")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request.body = JSON.generate({ url: 'https://internal.example.com/dashboard' })
res = http.request(request)
File.binwrite('dashboard.png', res.body)
puts 'Saved dashboard.png'
3. Inject Service Token headers
require 'net/http'
require 'json'
require 'uri'
TOKEN = 'YOUR_API_TOKEN_HERE'
uri = URI("https://production-sfo.browserless.io/content?token=#{TOKEN}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request.body = JSON.generate({
url: 'https://internal.example.com/dashboard',
setExtraHTTPHeaders: {
'CF-Access-Client-Id' => 'YOUR_CF_CLIENT_ID.access',
'CF-Access-Client-Secret' => 'YOUR_CF_CLIENT_SECRET',
},
})
res = http.request(request)
puts res.body[0, 200]
Use a browser connection to set Cloudflare Access headers before navigating to a protected page.
- Puppeteer
- Playwright
- Go (chromedp)
1. Install dependencies
npm install puppeteer-core
2. Inject Service Token headers
import puppeteer from 'puppeteer-core';
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE',
});
try {
const page = await browser.newPage();
await page.setExtraHTTPHeaders({
'CF-Access-Client-Id': 'YOUR_CF_CLIENT_ID.access',
'CF-Access-Client-Secret': 'YOUR_CF_CLIENT_SECRET',
});
await page.goto('https://internal.example.com/dashboard', {
waitUntil: 'networkidle2',
});
console.log('Title:', await page.title());
await page.screenshot({ path: 'dashboard.png' });
} finally {
// Always close to release the session even on error.
await browser.close();
}
3. Check the output
Run with node cf-access.mjs. The browser navigates directly to the protected page without an auth redirect.
- JavaScript
- Python
- Java
- C#
1. Install dependencies
npm install playwright-core
2. Inject Service Token headers
import { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP(
'wss://production-sfo.browserless.io?token=YOUR_API_TOKEN_HERE'
);
try {
const context = await browser.newContext({
extraHTTPHeaders: {
'CF-Access-Client-Id': 'YOUR_CF_CLIENT_ID.access',
'CF-Access-Client-Secret': 'YOUR_CF_CLIENT_SECRET',
},
});
const page = await context.newPage();
await page.goto('https://internal.example.com/dashboard', {
waitUntil: 'networkidle',
});
console.log('Title:', await page.title());
await page.screenshot({ path: 'dashboard.png' });
} finally {
// Always close to release the session even on error.
await browser.close();
}
3. Check the output
Run with node cf-access.mjs. The browser navigates directly to the protected page without an auth redirect.
1. Install dependencies
pip install playwright
2. Inject Service Token headers
from playwright.sync_api import sync_playwright
TOKEN = 'YOUR_API_TOKEN_HERE'
WS_ENDPOINT = f'wss://production-sfo.browserless.io?token={TOKEN}'
with sync_playwright() as playwright:
browser = playwright.chromium.connect_over_cdp(WS_ENDPOINT)
try:
context = browser.new_context(
extra_http_headers={
'CF-Access-Client-Id': 'YOUR_CF_CLIENT_ID.access',
'CF-Access-Client-Secret': 'YOUR_CF_CLIENT_SECRET',
}
)
page = context.new_page()
page.goto('https://internal.example.com/dashboard',
wait_until='networkidle')
print('Title:', page.title())
page.screenshot(path='dashboard.png')
finally:
# Always close to release the session even on error.
browser.close()
3. Check the output
Run with python cf-access.py. The browser navigates directly to the protected page without an auth redirect.
1. Install dependencies
Add the Playwright dependency to your pom.xml:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.44.0</version>
</dependency>
2. Inject Service Token headers
import com.microsoft.playwright.*;
import java.nio.file.Paths;
import java.util.Map;
public class CloudflareAccess {
public static void main(String[] args) {
String TOKEN = "YOUR_API_TOKEN_HERE";
String WS_ENDPOINT = "wss://production-sfo.browserless.io?token=" + TOKEN;
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connectOverCDP(WS_ENDPOINT);
try {
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().setExtraHTTPHeaders(Map.of(
"CF-Access-Client-Id", "YOUR_CF_CLIENT_ID.access",
"CF-Access-Client-Secret", "YOUR_CF_CLIENT_SECRET"
))
);
Page page = context.newPage();
page.navigate("https://internal.example.com/dashboard");
page.waitForLoadState(LoadState.NETWORKIDLE);
System.out.println("Title: " + page.title());
page.screenshot(new Page.ScreenshotOptions()
.setPath(Paths.get("dashboard.png")));
} finally {
// Always close to release the session even on error.
browser.close();
}
}
}
}
3. Check the output
Compile with mvn compile and run with mvn exec:java. The browser navigates directly to the protected page without an auth redirect.
1. Install dependencies
dotnet add package Microsoft.Playwright
2. Inject Service Token headers
using Microsoft.Playwright;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class CloudflareAccess
{
static async Task Main()
{
const string TOKEN = "YOUR_API_TOKEN_HERE";
string WS_ENDPOINT = $"wss://production-sfo.browserless.io?token={TOKEN}";
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectOverCDPAsync(WS_ENDPOINT);
try
{
var context = await browser.NewContextAsync(new()
{
ExtraHTTPHeaders = new Dictionary<string, string>
{
["CF-Access-Client-Id"] = "YOUR_CF_CLIENT_ID.access",
["CF-Access-Client-Secret"] = "YOUR_CF_CLIENT_SECRET",
}
});
var page = await context.NewPageAsync();
await page.GotoAsync("https://internal.example.com/dashboard",
new() { WaitUntil = WaitUntilState.NetworkIdle });
Console.WriteLine($"Title: {await page.TitleAsync()}");
await page.ScreenshotAsync(new() { Path = "dashboard.png" });
}
finally
{
// Always close to release the session even on error.
await browser.CloseAsync();
}
}
}
3. Check the output
Run with dotnet run. The browser navigates directly to the protected page without an auth redirect.
1. Install dependencies
go get github.com/chromedp/chromedp
go get github.com/chromedp/cdproto
2. Inject Service Token headers
package main
import (
"context"
"fmt"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/chromedp"
)
func main() {
token := "YOUR_API_TOKEN_HERE"
ws := fmt.Sprintf("wss://production-sfo.browserless.io?token=%s", token)
allocCtx, cancel := chromedp.NewRemoteAllocator(context.Background(), ws, chromedp.NoModifyURL)
defer cancel()
ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()
var title string
if err := chromedp.Run(ctx,
// Enable the network domain before setting headers.
network.Enable(),
network.SetExtraHTTPHeaders(network.Headers{
"CF-Access-Client-Id": "YOUR_CF_CLIENT_ID.access",
"CF-Access-Client-Secret": "YOUR_CF_CLIENT_SECRET",
}),
chromedp.Navigate("https://internal.example.com/dashboard"),
chromedp.WaitReady("body"),
chromedp.Title(&title),
); err != nil {
panic(err)
}
fmt.Println("Title:", title)
}
3. Check the output
Run with go run main.go. The browser navigates directly to the protected page without an auth redirect.
Next steps
- Save Logins to Authenticated Profiles — capture a user session after logging in through Cloudflare Access
- Solving Cloudflare Challenges — bypass Cloudflare bot challenges (not zero-trust access policies)
- Log In and Reuse Sessions — general session persistence pattern