A RESTful API built with .NET 9.0 for managing students, teachers, courses, classes, attendance, assignments, and grading with role-based access control.
- Complete JWT Authentication with token revocation and refresh token rotation
- Database-Validated Refresh Tokens with automatic expiration and revocation
- Redis Token Blacklist for immediate access token revocation
- Role-Based Authorization (Admin, Teacher, Student) with middleware enforcement
- Complete CRUD Operations for managing educational resources
- Pagination & Filtering for large datasets (Classes, Students, Assignments)
- Soft Delete for Users and Courses (preserves historical data)
- File Upload support for assignment submissions
- Redis Caching for improved performance
- Attendance Tracking with multiple status types
- Assignment Management with submission and grading capabilities
- Notification System for broadcasting messages
- Comprehensive Logging using Serilog
- API Documentation with Swagger/OpenAPI
- Data Validation using FluentValidation
- Access tokens with JTI claims (60-minute TTL)
- Refresh tokens stored in database (7-day TTL)
- Token blacklist with automatic cleanup (Redis TTL)
- Token validation middleware
- All tokens revoked on logout
- Password hashing with BCrypt
- SQL injection prevention
- .NET Core 9.0 - Backend framework
- SQL Server 2022 - Database
- Redis 7-Alpine - Caching & token blacklist
- Entity Framework Core 9.0 - ORM
- JWT Bearer - Authentication
- StackExchange.Redis - Redis client
- AutoMapper - Object mapping
- FluentValidation - Input validation
- Serilog - Logging
- Swagger/OpenAPI - API documentation
- BCrypt.NET - Password hashing
- Docker - Containerization
- .NET 9.0 SDK
- Docker Desktop (for running with containers)
- SQL Server (if not using Docker)
-
Clone the repository:
git clone <repository-url> cd SchoolManagementAPI
-
Build and start the containers:
docker-compose up --build
-
The API will be available at:
- HTTP:
http://localhost:8080 - Swagger UI:
http://localhost:8080
- HTTP:
-
Run database migrations:
dotnet ef database update
-
Clone the repository:
git clone <repository-url> cd SchoolManagementAPI
-
Update the connection string in
appsettings.json:"ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=SchoolManagementDb;User Id=sa;Password=YourPassword;TrustServerCertificate=True" }
-
Run database migrations:
dotnet ef database update
-
Run the application:
dotnet run
-
The API will be available at:
- HTTP:
http://localhost:5000 - HTTPS:
https://localhost:5001 - Swagger UI:
http://localhost:5000
- HTTP:
# Create a new migration
dotnet ef migrations add MigrationName
# Apply migrations to database
dotnet ef database update
# Remove last migration
dotnet ef migrations remove
# View migration SQL script
dotnet ef migrations script| Method | Endpoint | Description | Access |
|---|---|---|---|
| POST | /api/auth/register |
Register new user | Public |
| POST | /api/auth/login |
Login and get JWT token | Public |
| POST | /api/auth/refresh-token |
Refresh access token (validates against DB) | Public |
| POST | /api/auth/logout |
Logout and revoke all tokens | Authenticated |
Authentication Flow:
- Login returns access token (60min) + refresh token (7 days)
- Access token added to requests:
Authorization: Bearer {token} - When access token expires, use refresh token to get new tokens
- Logout revokes all tokens (access token blacklisted in Redis, refresh tokens revoked in DB)
- Tokens validated against database and Redis blacklist
| Method | Endpoint | Description | Access |
|---|---|---|---|
| GET | /api/admin/departments |
Get all departments | Admin |
| GET | /api/admin/departments/{id} |
Get department by ID | Admin |
| POST | /api/admin/departments |
Create department | Admin |
| PUT | /api/admin/departments/{id} |
Update department | Admin |
| DELETE | /api/admin/departments/{id} |
Delete department | Admin |
| GET | /api/admin/courses |
Get all courses | Admin |
| GET | /api/admin/courses/{id} |
Get course by ID | Admin |
| POST | /api/admin/courses |
Create course | Admin |
| PUT | /api/admin/courses/{id} |
Update course | Admin |
| DELETE | /api/admin/courses/{id} |
Delete course | Admin |
| Method | Endpoint | Description | Access |
|---|---|---|---|
| GET | /api/teacher/classes |
Get teacher's classes | Teacher |
| POST | /api/teacher/classes |
Create new class | Teacher |
| PUT | /api/teacher/classes/{id} |
Update class | Teacher |
| POST | /api/teacher/classes/{classId}/enroll |
Enroll student in class | Teacher |
| POST | /api/teacher/attendance |
Mark attendance | Teacher |
| GET | /api/teacher/attendance/{classId} |
Get class attendance | Teacher |
| POST | /api/teacher/assignments |
Create assignment | Teacher |
| GET | /api/teacher/assignments/{classId} |
Get class assignments | Teacher |
| POST | /api/teacher/assignments/{id}/grade |
Grade submission | Teacher |
| Method | Endpoint | Description | Access |
|---|---|---|---|
| GET | /api/student/classes |
Get enrolled classes | Student |
| GET | /api/student/attendance |
Get my attendance | Student |
| GET | /api/student/assignments |
Get my assignments | Student |
| POST | /api/student/assignments/{id}/submit |
Submit assignment | Student |
| GET | /api/student/grades |
Get my grades | Student |
| GET | /api/student/notifications |
Get notifications | Student |
POST /api/auth/register
Content-Type: application/json
{
"name": "John Doe",
"email": "john.doe@school.com",
"password": "Password123",
"role": "Teacher"
}Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "base64encodedtoken...",
"userId": 1,
"name": "John Doe",
"email": "john.doe@school.com",
"role": "Teacher"
}POST /api/auth/login
Content-Type: application/json
{
"email": "john.doe@school.com",
"password": "Password123"
}POST /api/admin/departments
Authorization: Bearer <your-jwt-token>
Content-Type: application/json
{
"name": "Computer Science",
"description": "Department of Computer Science and Engineering",
"headOfDepartmentId": 1
}POST /api/admin/courses
Authorization: Bearer <your-jwt-token>
Content-Type: application/json
{
"name": "Introduction to Programming",
"code": "CS101",
"description": "Fundamentals of computer programming",
"departmentId": 1,
"credits": 3
}POST /api/teacher/classes
Authorization: Bearer <your-jwt-token>
Content-Type: application/json
{
"name": "CS101-Fall2024-Section1",
"courseId": 1,
"teacherId": 1,
"semester": "Fall 2024",
"startDate": "2024-09-01T00:00:00Z",
"endDate": "2024-12-15T00:00:00Z"
}POST /api/teacher/attendance
Authorization: Bearer <your-jwt-token>
Content-Type: application/json
{
"classId": 1,
"studentId": 5,
"date": "2024-11-14T00:00:00Z",
"status": "Present"
}POST /api/teacher/assignments
Authorization: Bearer <your-jwt-token>
Content-Type: application/json
{
"classId": 1,
"title": "Programming Assignment 1",
"description": "Implement a basic calculator",
"dueDate": "2024-11-30T23:59:59Z"
}POST /api/student/assignments/1/submit
Authorization: Bearer <your-jwt-token>
Content-Type: application/json
{
"assignmentId": 1,
"fileUrl": "https://storage.example.com/submissions/student5/assignment1.zip"
}POST /api/teacher/assignments/1/grade
Authorization: Bearer <your-jwt-token>
Content-Type: application/json
{
"grade": 95.5,
"remarks": "Excellent work! Well-structured code."
}{
"ConnectionStrings": {
"DefaultConnection": "Server=sqlserver;Database=SchoolManagementDb;User Id=sa;Password=YourStrong@Passw0rd;TrustServerCertificate=True",
"RedisConnection": "redis:6379"
},
"JwtSettings": {
"SecretKey": "YourSecretKeyHere-MustBeLongEnough",
"Issuer": "SchoolManagementAPI",
"Audience": "SchoolManagementClient",
"ExpiryMinutes": 60,
"RefreshTokenExpiryDays": 7
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning"
}
}
}
}Key Configuration:
ExpiryMinutes: Access token lifetime (default: 60 minutes)RefreshTokenExpiryDays: Refresh token lifetime (default: 7 days)RedisConnection: Redis server for caching and token blacklistSecretKey: JWT signing key (must be at least 32 characters)
- Email must be unique and valid format
- Password must be at least 6 characters with uppercase, lowercase, and number
- Role must be Admin, Teacher, or Student
- Department names must be unique
- Head of Department must be a valid Teacher
- Cannot delete department with associated courses
- Course codes must be unique per department
- Credits must be between 1 and 10
- Cannot delete course with associated classes
- Only teachers can create and manage classes
- Students can only be enrolled by teachers
- Cannot enroll same student twice in same class
- Only assigned teacher can mark attendance
- Student must be enrolled in class
- Status must be: Present, Absent, or Late
- One attendance record per student per class per date
- Due date must be in the future
- Only assigned teacher can create assignments
- Only enrolled students can submit assignments
- Only assigned teacher can grade submissions
- JWT Access Tokens: 60-minute lifetime with JTI claims for unique identification
- Refresh Tokens: 7-day lifetime, stored and validated against database
- Token Blacklist: Redis-based blacklist for immediate access token revocation
- Token Validation Middleware: Automatic checking of blacklisted tokens on every request
- Token Rotation: New refresh token issued on each refresh, old one revoked
- Logout: All user tokens revoked (access token blacklisted, refresh tokens marked as revoked in DB)
- Role-Based Authorization: Granular access control (Admin, Teacher, Student)
- User Status Validation: Account deactivation prevents token usage
- Password Hashing: BCrypt with salt for secure password storage
- Input Validation: FluentValidation for all DTOs
- SQL Injection Prevention: Entity Framework parameterized queries
- Soft Delete: Historical data preservation with IsActive flag
- CORS Configuration: Configurable cross-origin resource sharing
- Access tokens include: userId, name, email, role, JTI (JWT ID)
- Refresh tokens validated on every use: exists in DB, not expired, not revoked, user active
- Automatic cleanup: Redis TTL ensures blacklisted tokens expire naturally
- All old tokens revoked on new login for security
Logs are written to:
- Console: All log levels in development
- File:
logs/schoolmanagement-{Date}.txtwith daily rolling
Log levels:
- Information: Application flow
- Warning: Validation failures, business rule violations
- Error: Exceptions and errors
- Fatal: Application crashes
Access the Swagger UI at the root URL to test all endpoints interactively:
- http://localhost:8080 (Docker)
- http://localhost:5000 (Local)
- Click the "Authorize" button
- Enter:
Bearer <your-jwt-token> - Click "Authorize"
- All subsequent requests will include the token
1. Register/Login:
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@example.com","password":"Password123"}'Save both token and refreshToken from response.
2. Use Access Token:
TOKEN="your-access-token"
curl -X GET http://localhost:8080/api/student/classes \
-H "Authorization: Bearer $TOKEN"Expected: Success (200 OK)
3. Refresh Token:
REFRESH_TOKEN="your-refresh-token"
curl -X POST http://localhost:8080/api/auth/refresh-token \
-H "Content-Type: application/json" \
-d "{\"refreshToken\":\"$REFRESH_TOKEN\"}"Expected: New tokens returned. Old refresh token is now revoked.
4. Logout:
curl -X POST http://localhost:8080/api/auth/logout \
-H "Authorization: Bearer $TOKEN"Expected: Success message
5. Verify Token Revoked:
curl -X GET http://localhost:8080/api/student/classes \
-H "Authorization: Bearer $TOKEN"Expected: 401 Unauthorized - "Token has been revoked"
6. Verify Refresh Token Revoked:
curl -X POST http://localhost:8080/api/auth/refresh-token \
-H "Content-Type: application/json" \
-d "{\"refreshToken\":\"$REFRESH_TOKEN\"}"Expected: 401 Unauthorized
Check Redis blacklist:
docker exec -it schoolmanagement-redis redis-cli
> KEYS SchoolManagement_blacklist:token:*
> GET SchoolManagement_blacklist:token:{JTI}
> TTL SchoolManagement_blacklist:token:{JTI}Check database refresh tokens:
-- Active tokens
SELECT * FROM RefreshTokens WHERE IsRevoked = 0;
-- Revoked tokens
SELECT * FROM RefreshTokens WHERE IsRevoked = 1;
-- User's tokens
SELECT * FROM RefreshTokens WHERE UserId = 1;# Build and start all containers (API, SQL Server, Redis)
docker-compose up --build
# Start containers (detached)
docker-compose up -d
# Stop containers
docker-compose down
# View logs
docker-compose logs -f
# View logs for specific service
docker-compose logs -f schoolmanagementapi
docker-compose logs -f redis
# Rebuild specific service
docker-compose up --build schoolmanagementapi
# Check container status
docker-compose ps
# Access SQL Server
docker exec -it schoolmanagement-sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P YourStrong@Passw0rd -C
# Access Redis CLI
docker exec -it schoolmanagement-redis redis-cli
# Monitor Redis (check blacklisted tokens)
docker exec -it schoolmanagement-redis redis-cli
> KEYS SchoolManagement_blacklist:token:*
> GET SchoolManagement_blacklist:token:{JTI}
> TTL SchoolManagement_blacklist:token:{JTI}Services Running:
- API: http://localhost:8080
- SQL Server: localhost:1433
- Redis: localhost:6379
- Ensure SQL Server container is healthy:
docker-compose ps - Check connection string in appsettings.json
- Verify SQL Server is accepting connections
- Ensure you're in the project directory:
cd SchoolManagementAPI - Check EF Core tools are installed:
dotnet tool install --global dotnet-ef - Delete Migrations folder and recreate if needed
"Token has been revoked" error:
- Token was blacklisted due to logout or security event
- Solution: Request new tokens using refresh token or login again
Refresh token not working:
- Check if token was revoked in database (
SELECT * FROM RefreshTokens WHERE Token = 'your-token') - Verify token not expired (
ExpiryDate > GETUTCDATE()) - Verify user account active (
SELECT IsActive FROM Users WHERE Id = userId) - Solution: Login again to get new tokens
Token validation failing:
- Verify token not expired (60-minute lifetime for access tokens)
- Check Authorization header format:
Bearer <token> - Ensure SecretKey in appsettings.json is at least 32 characters
- Check Redis is running for blacklist validation
Symptoms:
- Blacklist not working
- "RemoveByPrefix" errors in logs
- Cache not functioning
Solutions:
- Check Redis is running:
docker ps | grep redis - Test Redis connection:
docker exec -it schoolmanagement-redis redis-cli PING - Verify RedisConnection in appsettings.json:
"redis:6379" - Check Redis logs:
docker-compose logs redis
Verify blacklist is working:
# Login and get token
# Logout
# Try to use the token - should get 401
# Check Redis for blacklisted token
docker exec -it schoolmanagement-redis redis-cli
> KEYS SchoolManagement_blacklist:token:*Clear blacklist (testing only):
docker exec -it schoolmanagement-redis redis-cli
> KEYS SchoolManagement_blacklist:token:*
> DEL SchoolManagement_blacklist:token:{JTI}
# Or clear all:
> FLUSHDB- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a Pull Request
This project is licensed under the MIT License.