Handoff: RTSport Auth Layer (JWT + Role Claims)
What: Implement JWT authentication and role-based access control. Replace stub X-Role headers with real JWT tokens containing role claims.
Why: Integration testing is complete. The backend works with stub roles. Now we need real auth before production deployment.
Files:
- /home/hoffmann_admin/.openclaw/shared/build-20260501/backend/app/api/ — All endpoint files (roster.py, cases.py, events.py)
- /home/hoffmann_admin/.openclaw/shared/build-20260501/backend/app/config.py — Settings (secret_key, token expiry)
- /home/hoffmann_admin/.openclaw/shared/build-20260501/backend/app/database.py — Session management
- /home/hoffmann_admin/.openclaw/shared/build-20260501/backend/app/models.py — Need User model
Current State:
- Stubs: X-Role: at|coach|parent and X-User-ID: usr_001 headers
- FERPA filtering works with stubs
- Integration tests passed with stub roles
What You Need to Implement:
1. User Model
class User(Base):
__tablename__ = "users"
id = Column(String, primary_key=True)
school_id = Column(String, ForeignKey("schools.id"), nullable=False)
email = Column(String, unique=True, nullable=False)
hashed_password = Column(String, nullable=False)
role = Column(String, nullable=False) # "at" | "coach" | "parent" | "admin"
first_name = Column(String)
last_name = Column(String)
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
2. Auth Dependencies (FastAPI)
app/auth.py:
- create_access_token(user_id, role, school_id) → JWT string
- verify_token(token) → dict with user_id, role, school_id
- get_current_user(token: str = Header(...)) → User model
- require_role(roles: List[str]) → dependency factory
JWT Payload:
{
"sub": "usr_001",
"role": "at",
"school_id": "schl_001",
"exp": 1719878400
}
3. Update Endpoints
Replace all stub role checks with real auth:
# BEFORE (stub):
role = request.headers.get("X-Role", "at")
user_id = request.headers.get("X-User-ID", "usr_001")
# AFTER (JWT):
from app.auth import get_current_user, require_role
current_user: User = Depends(get_current_user)
# Role-protected endpoint:
@router.get("/roster")
async def get_roster(
current_user: User = Depends(require_role(["at", "coach"])),
db: Session = Depends(get_db)
):
# current_user.role, current_user.school_id available
...
4. Password Hashing
Use passlib with bcrypt:
- hash_password(password) → hashed string
- verify_password(plain, hashed) → bool
5. Login Endpoint
POST /api/v1/auth/login
// Request
{
"email": "at@preble.k12.wi.us",
"password": "..."
}
// Response
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"role": "at",
"school_id": "schl_001"
}
6. Parent Linking
Athlete's parent_ids array links to User IDs. When a parent logs in:
- JWT contains their user_id
- Cases endpoint filters: athlete.parent_ids.contains(current_user.id)
7. Update Integration Tests
Replace stub headers with JWT tokens in test scripts.
Scope:
- ✅ DO: Create User model
- ✅ DO: Implement JWT auth
- ✅ DO: Update all endpoints with real auth dependencies
- ✅ DO: Add login endpoint
- ✅ DO: Update integration tests
- ❌ DON'T: Change schemas or business logic
- ❌ DON'T: Write frontend auth UI (Daedalus handles that)
- ❌ DON'T: Add refresh tokens (out of scope)
Success Criteria:
- All endpoints require valid JWT
- Role-based access enforced (AT full, Coach team-scoped, Parent own-child)
- FERPA filtering uses real user role from JWT
- Integration tests pass with JWT tokens
- Passwords are hashed (never stored plain)
ETA: TBD — provide your own after review
Dependencies to Add:
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-multipart==0.0.9 (already in requirements.txt)
Coordination:
- Frontend will need to store JWT and send as Authorization: Bearer <token> header
- Daedalus should be notified when auth is ready
- Update integration.md with auth setup instructions