> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cycls.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Sign-in is a provider object. Clerk works out of the box, and any OIDC issuer works with a JWKS URL.

Auth providers are first-class objects. Hand one to `cycls.Web().auth(...)` for
agents, or to `@cycls.app(auth=...)` for apps, and the rest follows: sign-in
pages, protected routes, per-user storage and `context.user`.

```python theme={null}
web = cycls.Web().auth(cycls.Clerk())
```

Every provider supports a development and a production configuration, and Cycls
picks the right one based on whether you ran `deploy` or `run`.

## Clerk

The hosted default. No configuration needed to get started.

```python theme={null}
cycls.Clerk()

cycls.Clerk(one_tap=True)        # Google One Tap on the signed-out page

cycls.Clerk(                     # your own Clerk tenant
    jwks_url="https://clerk.mycompany.com/.well-known/jwks.json",
    dev_jwks_url="https://dev-clerk.mycompany.com/.well-known/jwks.json",
    pk="pk_live_...",
    dev_pk="pk_test_...",
)
```

<Note>
  `one_tap=True` needs Google enabled in Clerk with custom credentials, and every
  agent origin listed in that OAuth client's authorized JavaScript origins.
</Note>

## Any OIDC provider

```python theme={null}
cycls.JWT(
    jwks_url="https://my-prod.auth0.com/.well-known/jwks.json",
    dev_jwks_url="https://my-dev.auth0.com/.well-known/jwks.json",
)
```

Works with Auth0, WorkOS, Okta, Supabase, Keycloak and anything else that
publishes a JWKS endpoint. The default mapping takes `sub` as the user id.

## Firebase and GCP Identity Platform

```python theme={null}
cycls.GCP(project_id="my-project")
```

Maps `firebase.tenant` to `org_id`, so multi-tenant projects get the same
workspace semantics as Clerk organizations.

## The user object

```python theme={null}
user = context.user

user.id                  # stable user id
user.org_id              # organization id when acting in one
user.org_slug
user.org_role
user.org_permissions     # list
user.plan                # subscription tier from the JWT claim
user.features            # feature flags
user.name                # display name when the JWT carries one
user.image_url           # avatar when the JWT carries one
```

`context.user` is `None` when no provider is set. In apps, the same object comes
from the `auth` dependency:

```python theme={null}
@app.get("/me")
async def me(user=my_app.auth):
    return {"id": user.id, "plan": user.plan}
```

## Protecting your own routes

```python theme={null}
from fastapi import Depends


@my_agent.server.api_route("/admin/stats", methods=["GET"])
async def stats(user=Depends(my_agent.auth)):
    if user.org_role != "admin":
        return {"error": "forbidden"}
    return await collect_stats()
```

## What auth switches on

Auth switches on more than a login screen. Without a provider, an agent serves an anonymous
chat and the state routers are not installed. With one, you also get:

* per-user chats, files and key-value storage under `/workspace/<user_id>/`
* share links and forking
* [team workspaces](/web/workspaces)
* [connectors](/agents/connectors), since a grant belongs to a person or a team
* plan and feature gating through `user.plan` and `user.features`

## Plans

The Cycls-hosted Clerk app emits values like `u:free_user` and `o:free_org`,
where the prefix says whether the plan is a user plan or an organization plan,
and `cycls_pass` for paid subscribers. Your own tenant can emit anything.

```python theme={null}
if context.user.plan == "cycls_pass":
    yield "Premium features are enabled."
else:
    yield "Upgrade for full access."
    yield {"type": "ui", "action": "open_plan_modal"}
```

See [Monetization](/web/monetization) for quotas, plan gating and in-app
purchase entitlements.

## Next

<Card title="Workspaces" icon="users" href="/web/workspaces">
  Personal and team workspaces, and how data is partitioned.
</Card>
