# EduVerse API - Authentication Documentation for Flutter This document provides comprehensive details on the Authentication endpoints (`Register`, `Login`, `Refresh Token`, `Get Me`, and `Logout`). It is specifically designed to aid in the integration of the Flutter application, complete with expected request shapes, response formats, and Flutter/Dart implementation tips based on the backend DTO representations. --- ## Base URL Configuration Ensure you have the Base URL dynamically configured depending on your environment. - **Local Dev (Android Emulator)**: `http://10.0.2.2:8081` (Maps to your PC's `localhost:8081`) - **Local Dev (iOS Simulator / Web)**: `http://localhost:8081` - **Local Dev (Physical Device)**: `http://:8081` - **Production**: `` ## General Headers For all endpoints except public ones, the JWT `accessToken` must be passed in the `Authorization` header as follows: ```text Authorization: Bearer ``` --- ## 1. Register User **Endpoint:** `POST /api/auth/register` **Access:** Public **Description:** Creates a new user account. The default backend role is `student` unless explicitly provided. ### Request Body (`RegisterRequestDto`) ```json { "email": "student@example.com", // Required: Valid email string "password": "SecureP@ss123", // Required: Min 8 chars, 1 uppercase, 1 lowercase, 1 number, 1 special char (@$!%*?&) "firstName": "John", // Required: String (2-50 chars) "lastName": "Doe", // Required: String (2-50 chars) "phone": "+1234567890", // Optional: International phone number format "role": "student" // Optional: Enum ('student', 'instructor', 'ta', 'admin', 'it_admin') } ``` ### Success Response (HTTP 201 Created) Returns the created user data along with the session tokens. ```json { "user": { "userId": 1, "email": "student@example.com", "firstName": "John", "lastName": "Doe", "isEmailVerified": false, "roles": [{"roleName": "student"}] }, "accessToken": "eyJhbG...", "refreshToken": "eyJhbG..." } ``` ### Error Responses - **400 Bad Request**: Invalid body payload or password requirements not met. Check validation errors in JSON. - **409 Conflict**: User with this email already exists. --- ## 2. Login **Endpoint:** `POST /api/auth/login` **Access:** Public **Description:** Authenticates a user and returns a short-lived `accessToken` and a long-lived `refreshToken`. ### Request Body (`LoginRequestDto`) ```json { "email": "student@example.com", // Required: Valid email string "password": "SecureP@ss123", // Required: Password string "rememberMe": true // Optional: Extends refresh token expiry (e.g. from 7 to 30 days) } ``` ### Success Response (HTTP 200 OK) Returns user data, tokens, and token expiration data. ```json { "user": { "userId": 1, "email": "student@example.com", "firstName": "John", "lastName": "Doe", "roles": [ { "roleId": 1, "roleName": "student" } ] }, "accessToken": "eyJhbG...", "refreshToken": "eyJhbG...", "expiresIn": 900 // Expiration of access token in seconds (usually 15 minutes) } ``` ### Error Responses - **400 Bad Request**: Invalid credentials (wrong password or email). - **401 Unauthorized**: Account is disabled. --- ## 3. Refresh Token **Endpoint:** `POST /api/auth/refresh-token` **Access:** Public (uses the `refreshToken` in the body instead of a Bearer token) **Description:** Generates a new `accessToken` when the current one has expired. ### Request Body (`TokenRefreshRequestDto`) ```json { "refreshToken": "eyJhbG..." // Required: The persistent refresh token } ``` ### Success Response (HTTP 200 OK) Returns fresh tokens. Note that the backend might also rotate (provide a new) refresh token. Always update both tokens in your local secure storage. ```json { "accessToken": "new_eyJhbG...", "refreshToken": "new_or_same_eyJhbG...", "expiresIn": 900 } ``` ### Error Responses - **401 Unauthorized**: Invalid or expired refresh token. If you receive this, the session is permanently dead. Force the user to log out and return to the Login Screen. --- ## 4. Get Current User (Me) **Endpoint:** `GET /api/auth/me` **Access:** Protected (Requires `Bearer accessToken`) **Description:** Retrieves the complete profile of the currently authenticated user. Highly recommended to use this during application startup to verify the session and hydrate the BLoC/State. ### Request No body required. Must include Header: ```text Authorization: Bearer ``` ### Success Response (HTTP 200 OK) ```json { "userId": 1, "email": "student@example.com", "firstName": "John", "lastName": "Doe", "fullName": "John Doe", "phone": "+1234567890", "status": "active", "emailVerified": true, "createdAt": "2024-01-15T10:30:00.000Z", "roles": [ { "roleId": 1, "roleName": "student" } ] } ``` ### Error Responses - **401 Unauthorized**: Invalid or expired token. If this happens organically during app use, your local interceptor should automatically request `/api/auth/refresh-token` before showing an error. --- ## 5. Logout **Endpoint:** `POST /api/auth/logout` **Access:** Protected (Requires `Bearer accessToken`) **Description:** Invalidates the backend session to prevent token reuse. ### Request Body Must include Header: ```text Authorization: Bearer ``` Body: ```json { "refreshToken": "eyJhbG..." // Required: Invalidates this specific token on backend } ``` ### Success Response (HTTP 200 OK) ```json { "message": "Logged out successfully" } ``` --- ## Flutter & Dart Implementation Tips To assist you on the frontend side, here are recommended approaches for implementing this flow in Flutter: ### 1. Data Models (Using `freezed` & `json_serializable`) ```dart import 'package:freezed_annotation/freezed_annotation.dart'; part 'user_model.freezed.dart'; part 'user_model.g.dart'; @freezed class UserModel with _$UserModel { const factory UserModel({ required int userId, required String email, required String firstName, required String lastName, String? fullName, String? phone, required String status, @Default(false) bool emailVerified, @Default([]) List roles, }) = _UserModel; factory UserModel.fromJson(Map json) => _$UserModelFromJson(json); } @freezed class RoleModel with _$RoleModel { const factory RoleModel({ required int roleId, required String roleName, }) = _RoleModel; factory RoleModel.fromJson(Map json) => _$RoleModelFromJson(json); } ``` ### 2. Dio Interceptor for Token Refresh Flow Since the `accessToken` expires quickly (15 mins), you need an automated process to refresh the token mid-flight. Using a `Dio` interceptor is the standard and most resilient way: ```dart class AuthInterceptor extends Interceptor { final Dio dio; final SecureStorageRepository storage; // Your implementation of flutter_secure_storage AuthInterceptor(this.dio, this.storage); @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) async { final accessToken = await storage.getAccessToken(); if (accessToken != null) { options.headers['Authorization'] = 'Bearer $accessToken'; } return handler.next(options); } @override void onError(DioException err, ErrorInterceptorHandler handler) async { // If the server tells us our token is invalid (401) if (err.response?.statusCode == 401 && !err.requestOptions.path.contains('/auth/login')) { final refreshToken = await storage.getRefreshToken(); if (refreshToken != null) { try { // Attempt to fetch a new token final response = await dio.post('/api/auth/refresh-token', data: { 'refreshToken': refreshToken, }); final newAccessToken = response.data['accessToken']; final newRefreshToken = response.data['refreshToken']; // Save new tokens locally await storage.saveTokens( accessToken: newAccessToken, refreshToken: newRefreshToken ); // Retry the failed request with the new access token final requestOptions = err.requestOptions; requestOptions.headers['Authorization'] = 'Bearer $newAccessToken'; // Re-fetch failed request final cloneReq = await dio.fetch(requestOptions); return handler.resolve(cloneReq); } catch (e) { // Refresh token failed or is expired -> Fully Log Out user await storage.clearAll(); // TODO: Yield an unauthenticated state in your AuthBloc or dispatch a logout event. return handler.next(err); } } } return handler.next(err); } } ```