Shipfastai includes a complete authentication system out of the box. You get JWT-based login with short-lived access tokens and long-lived refresh tokens, OAuth sign-in with Google and GitHub, email verification on registration, and a two-step password reset flow. All auth endpoints live under the /api/auth prefix.
Access tokens expire after 30 minutes by default. Your client must use the refresh token endpoint to obtain a new access token before the current one expires, or prompt the user to log in again.
Registering a user
Send a POST request to /api/auth/register with the user’s email, password, and full name. On success, the server creates the account, sends a verification email, and returns the new user object. The user is not yet verified at this point.
POST /api/auth/register
Content-Type: application/json
{
"email": "ada@example.com",
"password": "hunter2secret",
"full_name": "Ada Lovelace"
}
{
"id": "a1b2c3d4-0000-0000-0000-000000000001",
"email": "ada@example.com",
"full_name": "Ada Lovelace",
"is_verified": false,
"is_active": true,
"created_at": "2026-04-09T10:00:00Z"
}
Logging in
Send a POST request to /api/auth/login with the user’s credentials. A successful response contains an access_token, a refresh_token, and the user object.
POST /api/auth/login
Content-Type: application/json
{
"email": "ada@example.com",
"password": "hunter2secret"
}
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": "a1b2c3d4-0000-0000-0000-000000000001",
"email": "ada@example.com",
"full_name": "Ada Lovelace",
"is_verified": false,
"is_active": true
}
}
Pass the access_token in the Authorization: Bearer <token> header on every subsequent request that requires authentication.
Refreshing tokens
Access tokens are short-lived. When one expires, send the stored refresh_token to /api/auth/refresh to get a new pair of tokens without requiring the user to log in again.
POST /api/auth/refresh
Content-Type: application/json
{
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Refresh tokens are valid for 7 days. After expiry, the user must log in again.
OAuth login
To start an OAuth flow, direct the user’s browser to GET /api/auth/oauth/{provider} where {provider} is either google or github. The endpoint returns an authorization URL and a state token.
{
"authorization_url": "https://accounts.google.com/o/oauth2/auth?client_id=...",
"state": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Redirect the user to authorization_url. After the user grants access, the provider sends them back to GET /api/auth/callback/{provider}?code=...&state=.... The backend exchanges the code for the user’s profile, creates or links the account, then issues tokens and redirects to:
{FRONTEND_URL}/auth/callback?access_token=<token>&refresh_token=<token>
OAuth users are automatically marked as verified. Accounts are matched by provider ID first, then by email address, so an existing email/password account is linked automatically if the OAuth email matches.
Email verification
After registering, the user receives an email containing a verification link. That link includes a short-lived token (valid for 24 hours). Submit that token to verify the account.
POST /api/auth/verify-email
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
{
"message": "Email verified successfully"
}
If the verification email expired or was lost, you can trigger a resend:
POST /api/auth/resend-verification
Content-Type: application/json
{
"email": "ada@example.com"
}
The endpoint always returns 200 regardless of whether the email is registered, to prevent user enumeration.
Password reset
Password reset is a two-step process.
Step 1 — request a reset link
POST /api/auth/forgot-password
Content-Type: application/json
{
"email": "ada@example.com"
}
{
"message": "If your email is registered, a password reset link has been sent"
}
The user receives an email with a reset link containing a token that is valid for 1 hour.
Step 2 — submit the new password
POST /api/auth/reset-password
Content-Type: application/json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"new_password": "newstrongerpassword"
}
{
"message": "Password reset successfully"
}
You can optionally validate the token before showing the form by calling POST /api/auth/verify-reset-token with { "token": "..." }. It returns { "valid": true } if the token is still usable.