Learn Cloudflare Turnstile Security Check Using Angular and Azure Functions

Sep 15, 2026SecurityEngineering
Summarize with AI: Google AI Claude ChatGPT Perplexity Grok

AI links open with a title + excerpt (these tools can't fetch the page themselves) — use "Copy full article" to paste the complete text for a fuller summary.

Share:

Introduction

This article demonstrates how to protect a public form using Cloudflare Turnstile in Angular and Azure Functions.

Our site had two open doors. The comment form writes to the database for anonymous visitors. The admin login accepts password attempts all day. Both needed a bot check.

Cloudflare Turnstile

Turnstile is a CAPTCHA from Cloudflare. The difference is that your visitor does not solve any puzzle. There are no traffic light images. It runs a few checks in the browser and shows a green tick.

The Turnstile security check showing Success

Features

  • No puzzle for the user in most cases.
  • Free for normal traffic.
  • No Google tracking on your visitors.
  • Works with any backend.

With the following steps, you can add Turnstile to your site.

  1. Create the site key and secret key.
  2. Add the widget in Angular.
  3. Verify the token in the API.
  4. Use the test keys for local development.

Create the Keys

Login to the Cloudflare dashboard. Go to Turnstile in the left side and click Add widget.

Add a Turnstile widget in the Cloudflare dashboard

Enter your domain name and select the Managed widget type. After you save, Cloudflare gives you two keys.

The site key and secret key for the widget

  • Site key — this goes in your Angular code. It is public and every visitor can see it. That is by design.
  • Secret key — this goes in your API settings only. Never put it in the frontend.

Add the Widget in Angular

The widget is a small standalone component, frontend/src/app/ui/turnstile.component.ts. It loads the Cloudflare script and gives back a token.

<tk-turnstile (token)="captchaToken.set($event)" />

Now send that token with your form data.

Verify the Token in the API

This step is the important one. The widget is not the security. The check in your API is the security.

Many samples add the widget, look at the green tick and stop there. An attacker does not run your Angular code. They call your API directly with a curl command. If you do not verify, the widget is only decoration.

// api/src/captcha.ts
const VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';

export async function verifyTurnstile(token: string, remoteIp?: string): Promise<boolean> {
  if (!token) return false;

  const params = new URLSearchParams();
  params.set('secret', getSecretKey());
  params.set('response', token);
  if (remoteIp && remoteIp !== 'unknown') params.set('remoteip', remoteIp);

  const response = await fetch(VERIFY_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: params,
  });

  const result = (await response.json()) as { success?: boolean };
  return result.success === true;
}

It is a small function, but note four points.

Return false for an empty token. No network call needed. An empty token is the most common bot request.

Compare with === true. Do not write if (result.success). If the response shape changes one day, a truthy value must not become a pass.

Send the IP only when you have one. Our client IP comes from the x-forwarded-for header and can be the text unknown. Cloudflare will reject that, so we skip the field.

Do not catch the error. If Cloudflare is down, fetch throws and the request fails. This is correct. If you write catch { return true; } to keep the site working, then a Cloudflare outage opens your door for everybody.

Now call it in the handler.

// api/src/functions/contact.ts
const captchaOk = await verifyTurnstile(body.captchaToken ?? '', getClientIp(request));
if (!captchaOk) {
  return { status: 400, jsonBody: { error: 'Captcha verification failed' } };
}

Check the position. It runs after the simple field checks, but before the database write, and on the login route before the bcrypt password compare. bcrypt is slow on purpose. Without the captcha in front, a bot can use your own password hashing to burn your CPU for free.

Keep the error message plain. Do not say whether the token was missing, expired or already used. That only helps the attacker.

Use the Test Keys Locally

Real Turnstile keys are tied to a real domain. If you use them on localhost, login stops working for the whole team.

Cloudflare publishes a test site key and secret that always pass. Use them in api/local.settings.json. You get the same widget and the same verify call, only the answer is always yes.

Warning: change both keys before you go live — the site key in the component and TURNSTILE_SECRET_KEY in the Azure Application Settings. If you deploy with the test pair, bots also see "Success". Put this on your deploy checklist.

Turnstile setting in Azure Static Web Apps Application Settings

Keep Your Old Protection Also

Our comment form already had a honeypot field and a simple rate limit. We did not remove them. Turnstile joined them.

Each one fails in a different way. The honeypot catches simple bots for free. The rate limit slows down volume. Turnstile makes automation expensive. If Turnstile has a bad day, the other two still work.

Conclusion

In this article we learned how to add a Cloudflare Turnstile security check, how to verify the token in an Azure Function, and why the verify step must fail closed.

Reference

#cloudflare#turnstile#captcha#azure-functions#angular#bot-protection

Comments

Be the first to comment.

Leave a comment

Never shown publicly.

Comments are reviewed before appearing publicly.