Event hooks¶
Subclass EventHooks (fastauth.EventHooks) and pass an instance to FastAuthConfig(hooks=...). Every method has a no-op default; override only what you need.
from fastauth import EventHooks
class MyHooks(EventHooks):
async def on_signup(self, user): await audit.log("signup", user["id"])
async def on_signin(self, user, provider): await audit.log("signin", user["id"], provider)
async def modify_jwt(self, token, user):
return {**token, "plan": "free"}
async def allow_signin(self, user, provider):
return user["email_verified"] or provider in ("google", "github")
config = FastAuthConfig(..., hooks=MyHooks())
Lifecycle hooks¶
| Hook | Default | Fired by |
|---|---|---|
on_signup(user) |
pass |
/auth/register, OAuth new user, magic-link auto-registration |
on_signin(user, provider) |
pass |
/auth/login, /auth/refresh (on_token_refresh is the more specific hook for refresh), OAuth callback, passkey complete (JSON mode only), magic-link callback (JSON mode only) |
on_signout(user) |
pass |
/auth/logout |
on_token_refresh(user) |
pass |
/auth/refresh (success path) |
on_email_verify(user) |
pass |
/auth/verify-email, OAuth email-verified-now branch, magic-link callback |
on_password_reset(user) |
pass |
/auth/reset-password, /auth/account/change-password |
on_oauth_link(user, provider) |
pass |
OAuth link/callback success |
on_magic_link_sent(user) |
pass |
After /auth/magic-links/login emails |
on_passkey_registered(user, passkey) |
pass |
Passkey registration complete |
on_passkey_deleted(user, passkey) |
pass |
Passkey deletion |
Cookie-mode note:
on_signinis currently invoked only in JSON mode for passkey complete and magic-link complete. In cookie mode the responses are emitted without firingon_signin. Audit accordingly. (needs maintainer clarification)
Gate hooks¶
| Hook | Default | Behavior on False |
|---|---|---|
allow_signin(user, provider) |
return True |
OAuth callback → 403; magic-link callback → 403; passkey complete → 403 |
allow_signinis NOT called by/auth/login(credentials). Only OAuth, magic-link, and passkey invoke it. If you need to gate credentials sign-in (e.g. onemail_verified), do it in your own route wrapper, since the credentials provider intentionally omits this hook.
Modify hooks¶
| Hook | Default | Used by |
|---|---|---|
modify_jwt(token, user) |
return token |
Every access and refresh token issuance. Return the modified payload dict. |
modify_session(session, user) |
return session |
Reserved for the future "database" session strategy; not currently wired. |
Common mistakes¶
- Forgetting to instantiate the hooks class —
hooks=MyHooks(class) does not work; passMyHooks()(instance). - Expecting
allow_signinto gate/auth/login— it does not. Document your gate explicitly if needed. - Returning a falsy value from
modify_jwt— the returned dict replaces the payload; returningNonewill break token signing.