Skip to content

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:

  1. The user sends their login and password to a special endpoint (e.g., /login).
  2. The server checks them. If everything is correct, it generates a unique, encrypted token (a long string) and sends it back.
  3. With each subsequent request to protected resources (e.g., POST /planets), the user must attach this token in the Authorization header.
  4. 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.

  1. Publish the configuration (if you haven't already):
    php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
    
  2. Run the migrations (will create the personal_access_tokens table):
    php artisan migrate
    
  3. Add the trait to the User model: Open app/Models/User.php and make sure it uses the HasApiTokens trait.
    // app/Models/User.php
    use Laravel\Sanctum\HasApiTokens;
    
    class User extends Authenticatable
    {
        use HasApiTokens, HasFactory, Notifiable;
        // ...
    }
    

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

pip install "python-jose[cryptography]" "passlib[bcrypt]" "python-multipart"

  • python-jose: for creating and verifying JWT tokens.
  • passlib: for hashing and verifying passwords.
  • python-multipart: for processing data from forms (username and password).

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.

  1. 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)
    

  2. 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

1. Stateless APIs most often use for authentication:

2. In Laravel, to protect routes with tokens, the middleware used is:

3. In FastAPI, to get data from a login form, the dependency used is:

4. How is a token passed from the client to the server in a protected request?


🚀 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 a 401 Unauthorized error.
  • Make a request to /login, get a token, add it to the Authorization header, and repeat the POST request. It should execute successfully.