Authentication
Serko Northsky uses Firebase Authentication for user identity management.
Overview
The authentication flow:
- User authenticates with Firebase (frontend)
- Firebase issues an ID token
- Frontend sends token in
Authorizationheader - Backend validates token with Firebase Admin SDK
- User identity is extracted from validated token
Frontend Authentication
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
const auth = getAuth();
// Sign in
const userCredential = await signInWithEmailAndPassword(auth, email, password);
const idToken = await userCredential.user.getIdToken();
// Use token in API requests
const response = await fetch('/api/users/profile', {
headers: {
'Authorization': `Bearer ${idToken}`,
'Content-Type': 'application/json',
},
});
Backend Validation
The backend validates tokens using Firebase Admin SDK:
from firebase_admin import auth as firebase_auth
async def verify_firebase_token(token: str) -> dict[str, Any]:
"""Verify Firebase ID token and return decoded claims."""
try:
decoded = firebase_auth.verify_id_token(token)
return decoded
except firebase_auth.InvalidIdTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
except firebase_auth.ExpiredIdTokenError:
raise HTTPException(status_code=401, detail="Token expired")
Authentication Dependency
Routes requiring authentication use the require_auth dependency:
from serko_northsky.api.v1.middlewares.auth_middleware import require_auth
@router.get("/profile")
async def get_profile(
auth: Annotated[dict[str, Any], Depends(require_auth)],
user_service: Annotated[BaseUserService, Depends(dependencies.get_user_service)],
) -> GetUserProfileResponse:
user = await user_service.get_by_auth_id(auth["uid"])
return GetUserProfileResponse.model_validate(user)
Auth Middleware
async def require_auth(
authorization: Annotated[str | None, Header()] = None,
) -> dict[str, Any]:
"""Require valid Firebase authentication."""
if not authorization:
raise HTTPException(status_code=401, detail="Missing authorization header")
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid authorization format")
token = authorization[7:] # Remove "Bearer " prefix
try:
decoded = await verify_firebase_token(token)
return decoded
except Exception as e:
raise HTTPException(status_code=401, detail=str(e))
Getting the Current User
async def get_authenticated_user(
auth: Annotated[dict[str, Any], Depends(require_auth)],
uow: Annotated[BaseUnitOfWork, Depends(APIContainer.get_unit_of_work)],
) -> User:
"""Get the authenticated user entity."""
user = await uow.users.get_by_field("auth_id", auth["uid"])
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
Token Claims
The decoded token contains:
{
"uid": "firebase-user-id",
"email": "user@example.com",
"email_verified": true,
"name": "John Doe",
"iat": 1642687200,
"exp": 1642690800,
"auth_time": 1642687200
}
User Registration
New users are created on first API call:
class UserService(BaseUserService):
async def get_or_create_user(self, auth_id: str, email: str) -> User:
"""Get existing user or create new one."""
existing = await self.uow.users.get_by_field("auth_id", auth_id)
if existing:
return existing
# Create new user
user = User(
auth_id=auth_id,
email=email,
language_id=await self._get_default_language_id(),
)
created = await self.uow.users.create(user)
await self.uow.save_changes()
return created
Token Refresh
Firebase tokens expire after 1 hour. The frontend should refresh proactively:
import { getAuth, onIdTokenChanged } from 'firebase/auth';
const auth = getAuth();
// Listen for token changes
onIdTokenChanged(auth, async (user) => {
if (user) {
const token = await user.getIdToken();
// Update stored token
setAuthToken(token);
}
});
// Force refresh before expiry
const forceRefresh = async () => {
const user = auth.currentUser;
if (user) {
const token = await user.getIdToken(true); // Force refresh
setAuthToken(token);
}
};
Error Responses
| Status | Error | Description |
|---|---|---|
| 401 | Missing authorization header | No token provided |
| 401 | Invalid authorization format | Not Bearer token |
| 401 | Invalid token | Token validation failed |
| 401 | Token expired | Token has expired |
| 404 | User not found | No user with auth_id |
Security Best Practices
- Always use HTTPS in production
- Validate tokens server-side — Never trust client claims
- Check email verification for sensitive operations
- Implement token refresh before expiry
- Log authentication events for security auditing
Related Documentation
- API Overview — General API information
- User Profile — User management