Skip to main content

Passport

vercel.passport reads and verifies Vercel Passport identities in Python functions.

uv add vercel-passport

Read the current visitor

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from vercel.passport import get_identity

app = FastAPI()


@app.get("/api/me")
async def me():
    identity = await get_identity()
    if identity is None:
        return JSONResponse({"authenticated": False}, status_code=401)

    return {
        "authenticated": True,
        "external_subject": identity.external_subject,
        "subject": identity.subject,
    }

get_identity() reads the x-vercel-oidc-passport-token header that Vercel adds after Passport authenticates the visitor, falling back to the _vercel_passport cookie. A session too large for one cookie is split across several, and is reassembled and checksummed before it is verified. With no arguments, it reads the headers the Vercel Python runtime records for the current request. You can also pass the request, or its headers, explicitly:

identity = await get_identity(request)
identity = await get_identity(request.headers, cookies=request.cookies)

A token from the request is always verified against Vercel's OIDC JWKS. Only the Passport issuer, https://passport.vercel.com/<owner>, is accepted, and the token must have the Passport shape, so a regular Vercel OIDC token is never accepted as a Passport identity.

By default the token must match this deployment's project and environment, read from VERCEL_PROJECT_ID and VERCEL_TARGET_ENV (or VERCEL_ENV). If neither can be determined, verification fails closed. Pass project_id, environment, owner_id, or audience to set the expectations explicitly. Time claims allow 60 seconds of clock skew.

Differences from @vercel/passport

Expectations compare by equality, so a backend cannot accidentally accept tokens minted for any project:

  • "*" is not a wildcard for project_id or environment; it matches only a token whose claim is literally "*".
  • Lists of accepted projects or environments are not supported. Call verify_identity() once per expected project instead.
  • Of jose's verify options, only audience is exposed, and the clock-skew allowance is fixed at 60 seconds. That allowance also applies to iat: a token claiming to be issued more than 60 seconds in the future is rejected, where jose does not check iat at all.
  • An unverified debugging token is accepted only in local development by default. See Local development.
  • The synthesized development identity is opt-in with VERCEL_PASSPORT_DEV=1 rather than on by default, so a process that is merely not on Vercel, such as a self-hosted server, never authenticates every visitor as a test user.
  • Local identities are set with the local_identity() context manager or VERCEL_PASSPORT_IDENTITY rather than getIdentity options, and are ignored on Vercel deployments other than VERCEL_ENV=development.
  • identity.project is set only when the token names a project ID, which every verified token does, so identity.project.id is never None.
  • In PassportDevelopmentIdentity, an explicit None clears a field its VERCEL_PASSPORT_DEV_* variable sets.

Identities are frozen Pydantic models, so an endpoint can return one directly, or serialize it with identity.model_dump(). The bearer token is left out of both model_dump() and the repr, so neither a response nor a log line exposes it; read it as identity.token when you need to forward it.

Use require_identity() when a route needs a real Passport token. It takes the same arguments as get_identity(), but raises PassportIdentityError instead of returning None, and also rejects an identity without a token, such as the local development identity. The returned identity's token is always set:

from vercel.passport import PassportIdentityError, require_identity

try:
    identity = await require_identity()
except PassportIdentityError:
    raise HTTPException(status_code=401)

forward_to_backend(identity.token)

For synchronous code, such as Flask or Django views, use vercel.passport.sync, which has the same names and arguments:

from vercel.passport.sync import get_identity

identity = get_identity()

Verify forwarded tokens

Use verify_identity() when a Passport-protected app forwards its Passport token to another backend. It accepts the token itself, or request headers carrying an Authorization: Bearer <token> header:

from fastapi import FastAPI, HTTPException, Request
from vercel.passport import PassportIdentityError, verify_identity

app = FastAPI()


@app.post("/api/orders")
async def orders(request: Request):
    try:
        identity = await verify_identity(
            request,
            owner_id="team_123",
            project_id="prj_123",
            environment="production",
        )
    except PassportIdentityError:
        raise HTTPException(status_code=401)

    return {"external_subject": identity.external_subject, "email": identity.email}

Unlike get_identity(), verify_identity() always verifies and never falls back to a local identity. If the token was minted by a different Vercel project than the backend verifying it, pass the source project's project_id and environment.

Local development

Local development usually has no Passport header or cookie, because Passport creates them in Vercel's edge network, so get_identity() returns None there. To work locally as a signed-in visitor, opt in to a synthesized test identity:

VERCEL_PASSPORT_DEV=1

get_identity() then returns a test identity when the request has no token, and emits a PassportDevelopmentIdentityWarning the first time it does so. The test identity uses:

external_subject: test-user
email: test-user@passport.local
name: Test User

Override individual fields with environment variables:

VERCEL_PASSPORT_DEV_OWNER=acme
VERCEL_PASSPORT_DEV_CONNECTOR_ID=scl_dev
VERCEL_PASSPORT_DEV_EXTERNAL_SUB=user_dev
VERCEL_PASSPORT_DEV_EXTERNAL_ISS=https://idp.example.com
VERCEL_PASSPORT_DEV_PROJECT=my-project
VERCEL_PASSPORT_DEV_PROJECT_ID=prj_local

To set the identity in code, such as in tests, use local_identity(). Inside the block, requests without a token get that identity, with no environment variable needed. Pass a PassportDevelopmentIdentity, whose unset fields still come from the environment, or the full claims yourself:

from vercel.passport import PassportDevelopmentIdentity, local_identity

with local_identity(PassportDevelopmentIdentity(owner="acme", external_subject="user_dev")):
    response = client.get("/api/me")

with local_identity({
    "typ": "passport",
    "iss": "https://passport.vercel.com/local",
    "owner": "local",
    "connector_id": "local",
    "external_sub": "local-user",
    "sub": "owner:local:connector:local:principal:local-user",
}):
    ...

The identity is held in a context variable, so it applies to the current request or test, to tasks started inside the block, and to threads started with anyio.to_thread or asyncio.to_thread, but not a plain threading.Thread. Full claims can also be given as JSON in VERCEL_PASSPORT_IDENTITY. The identity set with local_identity() comes first, then VERCEL_PASSPORT_IDENTITY, then the VERCEL_PASSPORT_DEV=1 test identity.

None of these is used on a Vercel deployment unless VERCEL_ENV=development, and none is verified, so identity.verified is False.

To debug with a real token copied from a deployed request, pass it directly or set VERCEL_PASSPORT_TOKEN. Such a token is decoded without verification unless you pass verify=True:

identity = await get_identity(token=copied_token)

Unverified tokens are accepted only in local development. On a Vercel deployment, other than VERCEL_ENV=development, an unverified token raises PassportIdentityError, including one left in VERCEL_PASSPORT_TOKEN. In deployed code, use verify_identity() or pass verify=True to verify explicit tokens. Pass allow_unverified=True only if you really mean to trust an unsigned token.

Metadata

Release files for vercel-passport 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for vercel-passport 0.1.0
File Size Uploaded Provenance Actions
vercel_passport-0.1.0.tar.gz 19.5 kB Without provenance Details for vercel_passport-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for vercel-passport 0.1.0
File Interpreter ABI Platform Actions
vercel_passport-0.1.0-py3-none-any.whl Python 3 none any Details for vercel_passport-0.1.0-py3-none-any.whl

Total release size: 41.8 kB

Release files / vercel_passport-0.1.0.tar.gz

Download URL vercel_passport-0.1.0.tar.gz
Size 19.5 kB
Tags Source
SHA-256 checksum
How to use checksums
2a05baf83ad87047055d79359a559850411ae02613303d52a9c6e931ddbd4b82
BLAKE2b-256 checksum
How to use checksums
361483643d08b6dfd27aaa0dc734e99a0fadb5a22f8a30578a2f672ee0e80788
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / vercel_passport-0.1.0-py3-none-any.whl

Download URL vercel_passport-0.1.0-py3-none-any.whl
Size 22.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
968bf82ccc418d5e6f142f225c24617db641d2f26c63843f25d546396a5c8aaa
BLAKE2b-256 checksum
How to use checksums
dc0b37f175980c18d192e31008db5ba647072dbcdc5ae35d099ef7dba2017da2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for vercel-passport 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page