Problem
There was no good way to share practice and competition video with athletes. Texting compresses the footage and kills quality, email caps attachment sizes, and apps like Snapchat combine the worst of both since the video is lossy and disappears. I wanted one place to upload and download full-quality video with role-based access so each athlete sees only their own footage while coaches can see everyone. It needed to be easy to use and coaches and athlete should not need to worry about the security of their data.
Approach
Every request is authenticated and authorized along the way. Uploads and downloads are handled by the browser using short-lived pre-signed URLs instead of going through the backend.
Request and authorization flow. The backend authenticates every request and signs a short-lived S3 URL; the video itself moves directly between the browser and the private bucket over HTTPS/TLS, so AWS credentials never reach the client.
Access control
Role-Based Access Control governs three roles. Athletes can view and download only their own footage, but can upload footage for other athletes, coaches can browse, upload, and download footage for every athlete, and admins can do everything a coach can as well as manage users and delete files. Before signing a download or streaming URL, the server checks that the requested object belongs to the user making the request so that no one can get ahold of someone else’s footage by making a guess.
Data protection in transit and at rest
All traffic for the application is served over HTTPS/TLS to protect it while in transit. The S3 bucket blocks public access and objects are encrypted at rest using AES-256 encryption. The pre-signed URLs used expire after 15 minutes, so that intercepted links cannot be saved for later use.
Credential isolation and app hardening
The AWS keys belong to a single IAM user with permissions to just the bucket use for the project and live only in server-side environment variables. Sessions are a JWT stored in an HTTP-only cookie to prevent token theft through XSS. User passwords are bcrypt-hashed, and all database access uses parameterized queries to prevent SQL injection.
Tools
React + Vite
Frontend
Node.js + Express
Backend / API
SQLite
User & role data
AWS S3 (pre-signed URLs)
Private video storage
JWT + bcrypt
Auth & password hashing
AWS IAM
Scoped storage credentials
What I learned
Using RBAC from the start instead of adding it afterwards saved a lot of time and prevented me from needing to redesign and rework the project after getting it working. By making the application secure from the start, it limited the number of things I needed to worry about.
The biggest lesson was that issuing a pre-signed URL is an authorization decision. Scoping every URL to the requesting user's folder prefix and validating key ownership server-side before signing prevents someone with the URL but not the actual user account from accessing footage.
What I would change
- Scope uploads to the right folder. Downloads and streaming are locked to each athlete's own folder, I would implement the same checks for uploads so that only users can upload to the spots the should be allowed to.
- Stronger password policy. I Would require passwords to be longer and prevent common passwords.
- Brute-force protection. Adding rate limiting and temporary account lockout for all users and multi-factor authentication for admin accounts would round increase security.
- Real audit trail. Enabling AWS CloudTrail and S3 server access logging so every access and configuration change is recorded for investigation purposes.
- Shorter URL lifetimes and testing. Tightening the pre-signed URL TTL for downloads and adding an automated test suite covering the access-scoping rules would ensure that a future change cannot silently widen them.