---
title: Flask
description: Read the signed-in user on every request, protect views with a decorator, serve server-rendered pages and receive webhooks in Flask.
---

```bash
pip install "zevauth[flask]"
```

This page covers what Flask adds. The client, the backend API and the
rules behind every refusal are on [Python](/sdk/python/).

## Setup

```python
import os
from flask import Flask
from zevauth import ZevAuth
from zevauth.flask import ZevAuthFlask

zevauth = ZevAuth(
    os.environ["ZEVAUTH_ENVIRONMENT_ID"],
    secret_key=os.environ["ZEVAUTH_SECRET_KEY"],
)

app = Flask(__name__)
ZevAuthFlask(app, zevauth)
```

With an app factory, create `ZevAuthFlask()` once and call
`init_app(app, zevauth)` inside the factory.

The extension sets `g.zevauth` on every request: the signed-in person, or
nobody. It never raises.

## Protecting views

```python
from flask import g
from zevauth.flask import require_auth

@app.get("/api/me")
@require_auth
def me():
    return {"userId": g.zevauth.user_id}

@app.post("/api/payouts")
@require_auth(require_mfa=True)
def payouts():
    ...
```

A refused request answers with the same JSON every ZevAuth library uses:
`unauthorized` (401), `session_task_pending` (403, with `pendingTasks`)
and `mfa_required` (403).

## Who is signed in

```python
from zevauth.flask import current_auth

auth = current_auth()      # the same object as g.zevauth
```

In Jinja templates: `{% if g.zevauth.is_signed_in %}`.

## Server-rendered pages

Turn the [session bridge](/concepts/session-bridge/) on and the extension
registers `POST /zevauth/session` and `GET /zevauth/handshake`:

```python
zevauth = ZevAuth(
    os.environ["ZEVAUTH_ENVIRONMENT_ID"],
    publishable_key=os.environ["ZEVAUTH_PUBLISHABLE_KEY"],
    session_bridge=True,
)
```

It then reads the session cookie on every request. When it has expired, a
page request is redirected through the handshake, which refreshes it in
the browser and returns. Flask-WTF's CSRF protection does not need to
cover the bridge endpoint: the bridge checks its own header and the
`Origin`. If you use `CSRFProtect`, exempt the `zevauth` blueprint.

## Webhooks

```python
from flask import request
from zevauth import ZevAuthWebhookError, construct_webhook_event

@app.post("/webhooks/zevauth")
def zevauth_webhook():
    try:
        event = construct_webhook_event(
            request.get_data(),                          # the raw bytes
            request.headers.get("zevauth-signature"),
            os.environ["ZEVAUTH_WEBHOOK_SECRET"],
        )
    except ZevAuthWebhookError:
        return "", 400
    ...
    return "", 204
```

Read `request.get_data()`, not `request.get_json()`: the signature covers
the exact bytes.

## Backend API

```python
user = zevauth.api.users.get(g.zevauth.user_id)
```
