Chapter 6.3: Basic Authentication
Study Time: 1 hour
1. API Authentication: Pass to Mission Control
Authentication is the process of verifying a user's identity. Unlike sites with sessions and cookies, stateless APIs typically use tokens.
The process looks like this:
- The user sends their login and password to a special endpoint (e.g.,
/login
). - The server checks them. If everything is correct, it generates a unique, encrypted token (a long string) and sends it back.
- With each subsequent request to protected resources (e.g.,
POST /planets
), the user must attach this token in theAuthorization
header. - The server checks the token for validity and, if it is correct, executes the request.
💡 Space Analogy:
- Login/password = Your biometric scan to get a pass.
- Token = An electronic pass (ID card) that you receive at the entrance to Mission Control.
Authorization: Bearer <token>
header = You apply your pass to the reader at each secure door.- Protected endpoints (POST, PUT, DELETE) = Doors to the server room or to the launch control console.
2. Authentication in Laravel: Sanctum
Laravel offers an elegant solution for API authentication — Laravel Sanctum. It is ideal for SPAs (single-page applications), mobile applications, and simple token-based APIs.
Step 1: Installing and Configuring Sanctum
Sanctum is already installed in a standard Laravel application, but let's check the configuration.
- Publish the configuration (if you haven't already):
- Run the migrations (will create the
personal_access_tokens
table): - Add the trait to the
User
model: Openapp/Models/User.php
and make sure it uses theHasApiTokens
trait.
Step 2: Creating an Endpoint for Issuing Tokens We need a route where the user will send their login/password.
Add to routes/api.php
:
// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;
Route::post('/login', function (Request $request) {
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The credentials do not match.'],
]);
}
// Return the token
return response()->json([
'token' => $user->createToken('api-token')->plainTextToken
]);
});
For testing, you can create a user via a seeder or Tinker.
Step 3: Protecting Routes
Now let's protect our CRUD operations. Let's change routes/api.php
:
// routes/api.php
use App\Http\Controllers\PlanetController;
// Public route for viewing planets
Route::get('/planets', [PlanetController::class, 'index']);
Route::get('/planets/{planet}', [PlanetController::class, 'show']);
// Group of protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::post('/planets', [PlanetController::class, 'store']);
Route::put('/planets/{planet}', [PlanetController::class, 'update']);
Route::delete('/planets/{planet}', [PlanetController::class, 'destroy']);
// Route for logging out (deleting the token)
Route::post('/logout', function (Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'You have been successfully logged out'], 200);
});
});
The auth:sanctum
middleware will check for a valid token in the Authorization
header.
3. Authentication in FastAPI: OAuth2 and JWT
FastAPI does not have a built-in authentication system, but it has powerful tools for implementing it. The de facto standard is OAuth2 with JWT tokens.
Step 1: Install Dependencies
python-jose
: for creating and verifying JWT tokens.passlib
: for hashing and verifying passwords.python-multipart
: for processing data from forms (username
andpassword
).
Step 2: Create a Security Module (security.py
)
It is good practice to put all authentication logic in a separate file.
Create the security.py
file:
# security.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta, timezone
# --- Settings ---
SECRET_KEY = "your-super-secret-key-that-is-long-and-random" # ⚠️ Replace with your own key!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# --- Utilities ---
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login")
# --- Functions ---
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# --- Dependency function for token verification ---
def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
# Here you can return the user from the DB, for now just return the name
return {"username": username}
Step 3: Integration into main.py
Now let's connect this to our application.
-
Create the
/login
endpoint:# main.py from fastapi.security import OAuth2PasswordRequestForm from fastapi import Depends, APIRouter from . import security # Import our module # ... your FastAPI code ... router = APIRouter(prefix="/api/v1") @router.post("/login") def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): # Here should be user verification in the DB # For example, we have one test user is_user_valid = (form_data.username == "testuser" and security.verify_password("testpass", security.get_password_hash("testpass"))) if not is_user_valid: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", ) access_token = security.create_access_token(data={"sub": form_data.username}) return {"access_token": access_token, "token_type": "bearer"} # ... app.include_router(router)
-
Protect the endpoints:
Use our
get_current_user
dependency.# main.py or in your planet router @router.post("/planets", status_code=status.HTTP_201_CREATED) def create_planet( planet: PlanetCreate, current_user: dict = Depends(security.get_current_user) # <-- Protection! ): # Planet creation logic... print(f"User {current_user['username']} is creating a planet.") # ... return new_planet # Protect PUT and DELETE in the same way
4. Using Tokens on the Frontend
Our frontend must now first obtain a token, save it (for example, in localStorage
), and attach it to every protected request.
Example in JavaScript (fetch
):
// 1. Log in
async function login(email, password) {
const response = await fetch('http://localhost:8001/api/login', { // Laravel API address
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email, password})
});
const data = await response.json();
if (data.token) {
localStorage.setItem('api_token', data.token); // Save the token
}
}
// 2. Make a protected request
async function createPlanet(planetData) {
const token = localStorage.getItem('api_token');
const response = await fetch('http://localhost:8001/api/planets', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` // <--- Attach the token!
},
body: JSON.stringify(planetData)
});
// ...
}
Reinforcement Quiz
🚀 Chapter Summary:
You have installed an "access control system" on your APIs. Now not just anyone can make changes to your "galactic database".
- ✅ Understood the principle of token-based authentication.
- 🔐 Implemented token issuance and route protection in Laravel Sanctum.
- ⚙️ Configured authentication based on OAuth2 and JWT in FastAPI.
- 🛰️ Learned how the frontend should save and use a token.
Your APIs have become not only functional, but also secure. However, for other developers to be able to use them, they need "operating instructions".
📌 Check:
- Try making a
POST
request to/api/planets
(in Laravel) or/api/v1/planets
(in FastAPI) without a token using Postman or Insomnia. You should get a401 Unauthorized
error.- Make a request to
/login
, get a token, add it to theAuthorization
header, and repeat thePOST
request. It should execute successfully.