JWT Authentication
Authentication¶
SRF provides login and Token generation through sanic-jwt, and provides an independent request middleware to verify Bearer Token, load the user, and write it to request.ctx.user.
Prerequisites¶
Before enabling JWT, set JWT_SECRET in Sanic configuration (no longer requiring the SECRET_KEY environment variable to be set before import):
from sanic import Sanic
from srf.config import settings
app = Sanic("MyApp")
app.config.JWT_SECRET = "Read from a secure environment variable"
settings.set_app(app)
Register Authentication Routes¶
It is recommended to use register_auth_urls(). It calls setup_auth() and registers routes for registration, logout, verification email, and GitHub login:
register_auth_urls() internally uses secret=app.config.JWT_SECRET, so ensure it's set.
Do not call setup_auth() before register_auth_urls(), otherwise it will cause duplicate initialization of sanic-jwt.
If only the sanic-jwt login endpoint is needed, you can initialize directly:
from srf.auth.viewset import setup_auth
jwt = setup_auth(
app,
secret=app.config.JWT_SECRET,
url_prefix="/api/auth",
login_path="login",
# Other keyword arguments are passed to sanic_jwt.Initialize
)
app.ctx.jwt = jwt # register_auth_urls() mounts it the same way
setup_auth() has fixed usage of SRF's own authenticate and retrieve_user, do not pass these names as keyword arguments again, otherwise it will cause duplicate parameter errors. If secret is missing, it throws ServerError("secret is required").
Login¶
The default login endpoint is POST /api/auth/login. It supports email or username:
# Email login
curl -X POST http://localhost:8000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"password123"}'
authenticate() uses UserLoginSchema (at least one of email or username), queries srf.auth.models.User, checks if the user is active and verifies the password, then puts user_id, username, and role name into the JWT payload.
Authentication Middleware¶
from srf.middleware.authmiddleware import set_user_to_request_ctx
@app.middleware("request")
async def auth_middleware(request):
await set_user_to_request_ctx(request)
Protected requests must carry:
The middleware decodes the Token using HS256 and app.config.JWT_SECRET, queries the user via retrieve_user(), and sets request.ctx.user. It returns 401 if the Token is missing, malformed, expired, invalid, or the user does not exist.
Public Endpoints¶
NON_AUTH_ENDPOINTS stores the last segment of the path, not the full URL:
For example, "products" will exempt both /api/products and /admin/products. It does not support prefix matching, nor can it distinguish HTTP methods. For applications with high security requirements, it is recommended to modify the middleware to use explicit (method, path) rules.
Get Current User¶
class ProductViewSet(BaseViewSet):
async def create(self, request):
user = self.get_current_user(request)
if user is None:
return json({"error": "Not logged in"}, status=401)
# ...
get_current_user() first returns request.ctx.user, and if it does not exist, tries request.auth.
Registration and Email Verification¶
register_auth_urls() registers the following endpoints:
POST /api/auth/registerPOST /api/auth/send-verification-emailPOST /api/auth/logoutGET /api/auth/social/github/loginGET /api/auth/social/callbackGET /api/auth/social/github/login_by_code
Registration and verification email rely on:
request.app.ctx.redissettings.EMAIL_CODE_REDIS(orapp.config.EMAIL_CODE_REDIS), default is"EMAIL_CODE"; Redis key format isEMAIL_CODE_{email}- Environment variables
FROM_EMAIL,SMTP_SERVER,SMTP_PORT,PASSWORD
Email-related APIs:
from srf.tools.email import send_email, send_verify_code
# Send mail: synchronous SMTP (do not call it for long directly on the event loop)
ok = send_email(
to_email="user@example.com",
subject="Verification Code",
content="Your verification code is: 12345",
)
# Registration verification codes only: fixed subject/body; runs send_email via asyncio.to_thread
ok = await send_verify_code("user@example.com", "12345")
Security Considerations¶
- HTTPS must be used in production environments, and
JWT_SECRETmust be read from a key management system. - The default code does not have token refresh or real logout/revocation mechanisms.
- GitHub OAuth uses random
stateand HMAC-signed cookies with timestamps to prevent tampering, and constant-time comparison is also performed during callback.