Skip to content

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_signin is currently invoked only in JSON mode for passkey complete and magic-link complete. In cookie mode the responses are emitted without firing on_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_signin is NOT called by /auth/login (credentials). Only OAuth, magic-link, and passkey invoke it. If you need to gate credentials sign-in (e.g. on email_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 classhooks=MyHooks (class) does not work; pass MyHooks() (instance).
  • Expecting allow_signin to 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; returning None will break token signing.