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 forproject_idorenvironment; 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, onlyaudienceis exposed, and the clock-skew allowance is fixed at 60 seconds. That allowance also applies toiat: a token claiming to be issued more than 60 seconds in the future is rejected, wherejosedoes not checkiatat 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=1rather 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 orVERCEL_PASSPORT_IDENTITYrather thangetIdentityoptions, and are ignored on Vercel deployments other thanVERCEL_ENV=development. identity.projectis set only when the token names a project ID, which every verified token does, soidentity.project.idis neverNone.- In
PassportDevelopmentIdentity, an explicitNoneclears a field itsVERCEL_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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.