Advanced Assessment Quiz#
This quiz covers all topics from the Advanced tier. Answer each question, then check your answers.
Section 1: JWT Fundamentals#
1. A JWT consists of three parts separated by dots. What are they?
A) Username, Password, Signature
B) Header, Payload, Signature
C) Token, Claims, Hash
D) Key, Value, Expiration
Answer
B — A JWT has three Base64URL-encoded parts:
Header: Algorithm and token type (
{"alg": "HS256", "typ": "JWT"})Payload: Claims — data about the user (
{"sub": "user_123", "exp": 1234567890})Signature: Verification hash (
HMAC-SHA256(header + "." + payload, secret))
2. What is the difference between HS256 and RS256 signing algorithms?
Answer
Algorithm |
Type |
Key |
Use Case |
|---|---|---|---|
HS256 |
Symmetric |
Same secret for signing and verification |
Single-service applications |
RS256 |
Asymmetric |
Private key signs, public key verifies |
Microservices, third-party verification |
HS256 is simpler — one secret key shared between issuer and verifier. RS256 is more secure for distributed systems — only the auth server has the private key; all other services verify with the public key.
3. A developer stores {"user_id": 42, "password": "secret123", "role": "admin"} in a JWT payload. What is wrong?
Answer
Never store sensitive data (passwords) in JWT payloads. JWTs are Base64URL-encoded, NOT encrypted. Anyone can decode the payload without the secret key — the signature only prevents tampering, not reading. Store only non-sensitive identifiers: {"sub": "42", "role": "admin"}.
Section 2: OAuth2 Framework#
4. What problem does OAuth2 solve that simple username/password authentication does not?
Answer
OAuth2 enables delegated authorization — a user can grant a third-party application limited access to their resources (on another service) without sharing their password. Example: “Sign in with Google” lets your app access the user’s Google profile without knowing their Google password.
5. Explain the OAuth2 Authorization Code flow in order.
Answer
User clicks “Login with Google” on your app
Your app redirects user to Google’s authorization endpoint with
client_id,redirect_uri,scopeGoogle shows consent screen; user approves
Google redirects back to your app with an authorization code
Your app’s backend exchanges the code for an access token (server-to-server call with
client_secret)Your app uses the access token to call Google APIs (e.g., get user profile)
Key: The authorization code is exchanged server-side so the client_secret is never exposed to the browser.
6. What is the difference between OAuth2 and OpenID Connect (OIDC)?
A) They are the same thing
B) OAuth2 handles authorization; OIDC adds authentication (identity) on top of OAuth2
C) OIDC replaces OAuth2 entirely
D) OAuth2 is for APIs; OIDC is for websites
Answer
B — OAuth2 is an authorization framework (grants access to resources). OIDC is a layer on top of OAuth2 that adds authentication (proves who the user is). OIDC introduces the ID Token (a JWT containing user identity claims like email, name, sub).
Section 3: Authentication Patterns#
7. Implement a FastAPI dependency that extracts and validates a JWT from the Authorization header.
Answer
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
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=["HS256"])
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
result = await db.execute(select(User).where(User.id == int(user_id)))
user = result.scalars().first()
if user is None:
raise credentials_exception
return user
8. Why should access tokens have a short expiration (e.g., 15 minutes) and refresh tokens have a longer expiration (e.g., 7 days)?
Answer
Access tokens are sent with every API request and stored in potentially vulnerable locations (browser memory, mobile storage). A short expiration limits the damage if stolen — the attacker only has 15 minutes of access.
Refresh tokens are used only to get new access tokens and should be stored more securely (HTTPOnly cookies, server-side). They have longer expiration to avoid forcing users to re-login frequently.
If an access token is stolen: attacker has 15 minutes. If a refresh token is stolen: revoke it server-side and force re-login.
Section 4: Unit Testing#
9. What is the purpose of TestClient in FastAPI testing?
Answer
TestClient (from httpx or starlette.testclient) sends HTTP requests to your FastAPI app without starting a real server. It simulates the full request/response cycle (routing, validation, dependency injection, middleware) in-memory, making tests fast and reliable.
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_get_users():
response = client.get("/users")
assert response.status_code == 200
assert isinstance(response.json(), list)
10. How do you test a protected endpoint that requires JWT authentication?
Answer
Two approaches:
Approach 1: Override the dependency
async def override_get_current_user():
return User(id=1, email="test@example.com", name="Test User")
app.dependency_overrides[get_current_user] = override_get_current_user
Approach 2: Send a real token
def test_protected_endpoint():
# Create a valid token for testing
token = jwt.encode({"sub": "1", "exp": datetime.utcnow() + timedelta(hours=1)}, SECRET_KEY)
response = client.get("/tickets", headers={"Authorization": f"Bearer {token}"})
assert response.status_code == 200
Approach 1 is simpler for most tests. Approach 2 is needed when testing the auth flow itself.
11. How do you mock an async database session in tests?
Answer
Use a separate test database with dependency override:
# conftest.py
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
test_engine = create_async_engine("sqlite+aiosqlite:///./test.db")
async def override_get_db():
async with AsyncSession(test_engine) as session:
yield session
@pytest.fixture(autouse=True)
def setup_db():
app.dependency_overrides[get_db] = override_get_db
# Create tables before tests
async def init():
async with test_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
asyncio.run(init())
yield
# Cleanup
app.dependency_overrides.clear()
12. Write a test that verifies creating a ticket returns 201 and the correct data.
Answer
def test_create_ticket():
# Arrange: Override auth dependency
app.dependency_overrides[get_current_user] = lambda: User(id=1, name="Test")
# Act: Create a ticket
response = client.post("/api/v1/tickets", json={
"content": "My laptop is not working",
"description": "Screen is black after boot",
})
# Assert: Check status and response shape
assert response.status_code == 201
data = response.json()
assert data["content"] == "My laptop is not working"
assert data["status"] == "pending"
assert "ticket_id" in data # UUID was auto-generated
assert data["user_id"] == 1 # Assigned from auth