# OpenWAF Verify open source

> The browser side of OpenWAF Verify, its protocol and its siteverify libraries are open source under Apache-2.0. What is open, what is not, and why.

The client side of OpenWAF Verify is open source under the Apache License 2.0: [github.com/openwaf-dev/verify](https://github.com/openwaf-dev/verify). That is the code that runs in your visitors' browsers, the protocol it speaks with the edge, and the libraries your server uses to check tokens.

You can read what the [Verify widget](https://developers.openwaf.dev/waf/verify-widget/) and the [browser challenge](https://developers.openwaf.dev/waf/browser-challenge/) do in a visitor's browser, check it against what your site serves, and use the libraries instead of writing your own siteverify calls.

## What is open

- **Browser widget** (`challengejs/`). The `widget.js` script: the box, its states, the `owafVerify` API and the hidden `owaf-verify-response` field.
- **Proof-of-work worker** (`challengejs/`). The Web Worker that solves the SHA-256 puzzles, and the fallback that runs on the page without a worker.
- **Interstitial page** (`challengejs/`). The HTML template and CSS of the [browser challenge](https://developers.openwaf.dev/waf/browser-challenge/) page.
- **Protocol** (`spec/`). How the browser gets a challenge, sends its solution and receives a token, and the [siteverify](https://developers.openwaf.dev/waf/siteverify/) API.
- **Siteverify libraries.** Go (`siteverify/`, module `github.com/openwaf-dev/verify`), Node.js and TypeScript (`node/`, `@openwaf/verify`) and PHP (`php/`, Composer package `openwaf/verify`).
- **Components.** React (`react/`, `@openwaf/verify-react`) and Vue 3 (`vue/`, `@openwaf/verify-vue`).
- **Examples** (`examples/`). Small working apps that render the widget and check the token.

## What is not open, and why

Some parts stay closed:

- **Risk scoring.** How the edge turns [server-side signals](https://developers.openwaf.dev/waf/verify-risk/) into a score.
- **The browser-consistency rule.** How the edge checks that a browser behaves like the browser it says it is.
- **The in-page automation checks.** The module of yes/no checks about automation tools, and how its answer is encoded for each challenge.
- **Reputation and threat feeds.** The lists and sources behind [threat feeds](https://developers.openwaf.dev/waf/threat-feeds/) and reputation.
- **Edge internals.** The proxy and how its nodes work.

These parts decide how hard the edge looks at a request. If their exact rules were public, automation could be tuned to pass them, and each one would stop working on the day it was published.

What they read is not secret. The data each part processes is listed in [Data processed](https://developers.openwaf.dev/waf/verify-widget/#data) and in [risk scoring](https://developers.openwaf.dev/waf/verify-risk/#data).

## The served widget is not byte-identical

The widget your edge serves is this open code plus one closed extension module: the in-page automation checks.

- The module runs a few yes/no checks in the page.
- Its only output is one opaque integer, added to the solve request.
- It reads no fingerprinting surface: no canvas, WebGL, audio, fonts or high-entropy client hints.

Because of that module, a build from the repository is not byte-identical to the bundle at `/__waf/challenge/v1/widget.js`. Everything else in that bundle is the code in `challengejs/`.

## Check it yourself

You don't have to take our word for it. Open a page that uses Verify, such as the [live demo](https://developers.openwaf.dev/demo/verify/), with your browser's developer tools:

1. **Network.** Every request the widget makes goes to the page's own host, under `/__waf/`. Nothing goes to another domain.
2. **Cookies.** The widget sets no cookie. The browser challenge page sets only `__waf_pass`, after a successful solve.
3. **Content Security Policy.** The widget works with `Content-Security-Policy: default-src 'self'`, with no violations in the console. The demo runs under that policy.
4. **Source.** Read the served `widget.js` and `worker.js` and compare them with `challengejs/` in the repository.


## Use the libraries

The libraries call siteverify for you. They follow the [siteverify reference](https://developers.openwaf.dev/waf/siteverify/) and behave the same in every language. For now they install from the GitHub repository: Go and Composer from the `v0.1.0` tag, npm from the tarballs attached to the [release](https://github.com/openwaf-dev/verify/releases/tag/v0.1.0). The npm and Packagist packages are coming soon.

**Go**

```sh
go get github.com/openwaf-dev/verify/siteverify@v0.1.0
```
```go
c, err := siteverify.New(os.Getenv("OWAF_VERIFY_SECRET"))
if err != nil {
	log.Fatal(err)
}

res, err := c.Verify(r.Context(), r.FormValue("owaf-verify-response"), siteverify.Params{
	RemoteIP:       clientIP,
	ExpectedAction: "signup",
})
if err != nil || !res.Success {
	http.Error(w, "verification failed", http.StatusForbidden)
	return
}
// create the account
```
**Node**

```sh
npm install https://github.com/openwaf-dev/verify/releases/download/v0.1.0/openwaf-verify-0.1.0.tgz
```
```ts
import { SiteverifyClient } from "@openwaf/verify";

const client = new SiteverifyClient({ secret: process.env.OWAF_VERIFY_SECRET! });

const result = await client.verify(token, { remoteIp, expectedAction: "signup" });
if (!result.success) {
  // reject the form
}
```
**PHP**

```sh
composer config repositories.openwaf-verify vcs https://github.com/openwaf-dev/verify
composer require openwaf/verify:^0.1
```
```php
<?php
$client = new OpenWAF\Verify\Siteverify\Client((string) getenv('OWAF_VERIFY_SECRET'));

$result = $client->verify(
    $_POST['owaf-verify-response'] ?? '',
    remoteIp: $clientIp,
    expectedAction: 'signup',
);
if (!$result->success) {
    http_response_code(403);
    exit('Verification failed');
}
// create the account
```
**React**

```sh
npm install https://github.com/openwaf-dev/verify/releases/download/v0.1.0/openwaf-verify-react-0.1.0.tgz
```
```tsx
import { Verify } from "@openwaf/verify-react";

<form action="/signup" method="post">
  <input name="email" type="email" required />
  <Verify siteKey="owk_YOUR_SITE_KEY" action="signup" onSuccess={(token) => setToken(token)} />
  <button type="submit">Sign up</button>
</form>
```
**Vue**

```sh
npm install https://github.com/openwaf-dev/verify/releases/download/v0.1.0/openwaf-verify-vue-0.1.0.tgz
```
```html
<script setup>
import { Verify } from "@openwaf/verify-vue";
</script>

<template>
  <form action="/signup" method="post">
    <input name="email" type="email" required />
    <Verify site-key="owk_YOUR_SITE_KEY" action="signup" @success="onToken" />
    <button type="submit">Sign up</button>
  </form>
</template>
```


The React and Vue components render the same widget, so the page must be served through OpenWAF.

What the server libraries do:

- **Defaults.** They call `https://dash.openwaf.dev/v1/challenge/siteverify` with a 10-second timeout. You can point them at `https://<your host>/__waf/challenge/siteverify` instead.
- **No request when it can't succeed.** An empty token returns `missing-input-response` and a token longer than 2048 bytes returns `invalid-input-response`, without calling siteverify.
- **Host name and action.** When you pass an expected host name or action and the token doesn't match, the answer is a failure with `hostname-mismatch` or `action-mismatch`. These two codes come from the library, not from siteverify.
- **Answers and errors.** Every siteverify answer, including `400`, `405` and `429` (with its `Retry-After`), comes back as a result. A timeout, a network failure, another HTTP status or a body that isn't JSON is an error: sentinel errors in Go, `SiteverifyError` in Node and `SiteverifyException` in PHP, with the codes `timeout`, `network`, `http` and `bad-response`.
- **No automatic retries.** To retry after a timeout, send the same [idempotency key](https://developers.openwaf.dev/waf/siteverify/#idempotency) again: the second call gets the first answer instead of `timeout-or-duplicate`.
- **The secret stays out of errors.** It never appears in error messages, logs or printed objects.

Try them with the [test keys](https://developers.openwaf.dev/waf/verify-widget/#test-keys): `ows_test_pass`, `ows_test_fail` and `ows_test_spent` work without a widget.

## Licence

Everything in the repository is under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). You can use, change and redistribute it, including in commercial products.

## Contribute

Open issues and pull requests at [github.com/openwaf-dev/verify](https://github.com/openwaf-dev/verify). We review every change. Changes to the browser code are released with the edge, so a merged change reaches sites with the next edge release.

## Report a vulnerability

Don't open a public issue. Write to `support@openwaf.dev` (`security@openwaf.dev` reaches the same team) and follow the repository's [SECURITY.md](https://github.com/openwaf-dev/verify/blob/main/SECURITY.md).
