AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Auth

skill-benja-pauls-serpentstack-auth · by Benja-Pauls

Understand and customize authentication in this project. Use when: adding auth to routes, swapping auth providers (Clerk, Auth0, custom SSO), debugging auth issues, or understanding the UserInfo contract and get_current_user dependency.

No reviews yet
0 installs
17 views
0.0% view→install

Install

$ agentstack add skill-benja-pauls-serpentstack-auth

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-benja-pauls-serpentstack-auth)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Auth? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Auth

SerpentStack ships with working local JWT authentication (register, login, token validation). This skill explains the auth architecture and how to swap providers.

How Auth Works (Built-in)

Architecture

POST /api/v1/auth/register  →  UserService.register()  →  bcrypt hash  →  DB  →  JWT
POST /api/v1/auth/login     →  UserService.authenticate()  →  verify hash  →  JWT
GET  /api/v1/auth/me        →  get_current_user dependency  →  decode JWT  →  UserResponse

Any protected route:
    @router.delete("/{id}")
    async def delete(user: UserInfo = Depends(get_current_user)):
        # user.user_id, user.email available here

Key Files

| File | Role | |---|---| | backend/app/routes/auth.py | Auth routes + get_current_user dependency | | backend/app/services/user.py | Registration, authentication, password hashing | | backend/app/models/user.py | User SQLAlchemy model (email, hashed_password) | | backend/app/schemas/user.py | Request/response schemas (register, login, token) | | backend/app/middleware/auth.py | Optional global auth middleware (not enabled by default) |

The UserInfo Contract

All protected routes receive a UserInfo object via dependency injection:

class UserInfo(BaseModel):
    user_id: str
    email: str | None = None
    name: str | None = None
    raw_claims: dict[str, Any] = {}

This is the interface between auth and the rest of the app. When swapping providers, keep this shape — every route that uses Depends(get_current_user) depends on it.

Protecting a Route

Add Depends(get_current_user) to any route that requires authentication:

from app.routes.auth import UserInfo, get_current_user

@router.post("")
async def create_thing(
    payload: ThingCreate,
    user: UserInfo = Depends(get_current_user),  # ← requires valid JWT
    db: AsyncSession = Depends(get_db),
    service: ThingService = Depends(get_thing_service),
) -> ThingResponse:
    thing = await service.create(payload, owner_id=user.user_id)
    await db.commit()
    return ThingResponse.model_validate(thing)

For optional auth (authenticated if token present, anonymous otherwise):

from app.routes.auth import get_optional_user

@router.get("")
async def list_things(
    user: UserInfo | None = Depends(get_optional_user),
) -> list[ThingResponse]:
    # user is None if no token, UserInfo if authenticated
    ...

Swapping to an External Provider

To replace the built-in JWT auth with Clerk, Auth0, or another provider:

Step 1: Replace get_current_user in routes/auth.py

The only function you need to change is get_current_user. Replace JWT decode with your provider's token validation:

For Clerk — see Clerk FastAPI guide:

from jwt import PyJWKClient

jwks_client = PyJWKClient("https://your-clerk-domain/.well-known/jwks.json")

async def get_current_user(
    credentials: HTTPAuthorizationCredentials | None = Security(bearer_scheme),
) -> UserInfo:
    if credentials is None:
        raise HTTPException(status_code=401, detail="Not authenticated")

    token = credentials.credentials
    signing_key = jwks_client.get_signing_key_from_jwt(token)
    payload = jwt.decode(token, signing_key.key, algorithms=["RS256"])

    return UserInfo(
        user_id=payload["sub"],
        email=payload.get("email"),
        name=payload.get("name"),
        raw_claims=payload,
    )

For Auth0 — see Auth0 FastAPI guide:

jwks_client = PyJWKClient("https://your-tenant.auth0.com/.well-known/jwks.json")

async def get_current_user(...) -> UserInfo:
    # Same pattern, add audience validation:
    payload = jwt.decode(
        token, signing_key.key, algorithms=["RS256"],
        audience="your-api-audience",
        issuer="https://your-tenant.auth0.com/",
    )
    return UserInfo(user_id=payload["sub"], email=payload.get("email"), ...)

Step 2: Remove unused files (optional)

If you no longer need local registration/login:

  • Remove backend/app/services/user.py
  • Remove backend/app/models/user.py (and its import in models/__init__.py)
  • Remove backend/app/schemas/user.py
  • Remove the /register and /login routes from routes/auth.py
  • Remove passlib[bcrypt] from pyproject.toml

Step 3: Update environment variables

Add your provider's config to .env:

# For Clerk:
CLERK_JWKS_URL=https://your-clerk-domain/.well-known/jwks.json

# For Auth0:
AUTH0_DOMAIN=your-tenant.auth0.com
AUTH0_AUDIENCE=your-api-audience

What stays the same

  • UserInfo shape — all routes keep working
  • Depends(get_current_user) pattern — no route changes needed
  • get_optional_user — works with any provider
  • Frontend token storage pattern — still sends Authorization: Bearer

Testing Auth

# Register a user
curl -X POST http://localhost:8000/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "password": "testpass123"}'

# Login
curl -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "password": "testpass123"}'

# Use the token
TOKEN=""
curl http://localhost:8000/api/v1/auth/me \
  -H "Authorization: Bearer $TOKEN"

# Protected route (delete item)
curl -X DELETE http://localhost:8000/api/v1/items/ \
  -H "Authorization: Bearer $TOKEN"

Troubleshooting

| Problem | Cause | Fix | |---|---|---| | 401 on every request | Missing or malformed Authorization: Bearer header | Check header format, ensure token isn't expired | | 422 on register | Password too short or invalid email | Password must be ≥8 chars, email must be valid | | 409 on register | Email already taken | Use a different email or login instead | | jwt.InvalidTokenError in logs | Token signed with wrong key or expired | Check SECRET_KEY matches between token creation and validation |

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.