Skip to content

Sudo mode

A logged-in session proves who the user is, but not that they're still at the keyboard right now. Sudo mode adds a short re-authentication step in front of sensitive actions (close account, rotate keys, change billing), so a session left open can't be used to do real damage.

from crudauth import CRUDAuth, SessionTransport, SudoConfig

auth = CRUDAuth(
    session=get_session, user_model=User, SECRET_KEY="change-me",
    transports=[SessionTransport()],
    sudo=SudoConfig(window_seconds=300, max_attempts=3, lockout_seconds=900),
)

Elevation is stamped on the server-side session, so it dies with the session (logout or revocation clears it). That's why sudo requires a SessionTransport; configuring it without one raises at startup.

Elevating

There's no built-in sudo route, because the prompt is part of your UI. Build a small endpoint that re-verifies the password and elevates the current session:

from pydantic import BaseModel

class SudoIn(BaseModel):
    password: str

@app.post("/sudo")
async def sudo(body: SudoIn, request: Request, user: Principal = Depends(auth.current_user())):
    until = await auth.sudo.elevate(user, body.password, request=request)
    return {"elevated_until": until.isoformat()}

elevate() re-checks the password and stamps the session with an absolute expiry (window_seconds from now). A wrong password raises 401; a non-session credential raises 403. With two-factor authentication, an enrolled user can pass an authenticator code instead: elevate(user, code=body.code, db=db, request=request).

Gating an action

Add auth.require_sudo() to any route that needs a recent re-auth. It passes only while the session holds an unexpired elevation, and returns 403 otherwise:

@app.post("/account/close")
async def close_account(
    user: Principal = Depends(auth.current_user()),
    _: Principal = Depends(auth.require_sudo()),
):
    ...   # reached only within the sudo window

Lockout

Repeated wrong passwords trip a dedicated sudo lockout, keyed separately from the login lockout so one can't mask the other. Once tripped, elevate() raises SudoLockoutError (429 with a Retry-After header), and the elevation on the session that tripped it is cleared. Other sessions keep an elevation they already hold until it expires. Attempts are counted before the password is checked, so concurrent guesses can't exceed the limit.

Auditing

An on_after_sudo hook fires on every successful elevation, which is a good place to write an audit record:

from crudauth import AuthHooks

async def on_sudo(user, *, request, context):
    await audit_log("sudo", user_id=user["id"])

auth = CRUDAuth(..., hooks=AuthHooks(on_after_sudo=on_sudo))

Next: Two-factor authentication →