# Me API Implementation (User Profile)
This document describes the implementation of the Me API endpoint for the MaplePress frontend, integrated with the MaplePress backend API.
## Overview
The Me API endpoint returns the authenticated user's profile information extracted directly from the JWT token. This is a lightweight endpoint that requires no database queries, making it ideal for displaying user information in the dashboard and verifying the current user's identity.
## Backend API Endpoint
**Endpoint**: `GET /api/v1/me`
**Authentication**: Required (JWT token)
**Documentation**: `/cloud/maplepress-backend/docs/API.md` (lines 374-413)
### Request Structure
No request body required. Authentication is provided via the Authorization header:
**Headers Required:**
- `Authorization: JWT {access_token}`
### Response Structure
```json
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"email": "john@example.com",
"name": "John Doe",
"role": "owner",
"tenant_id": "650e8400-e29b-41d4-a716-446655440000"
}
```
### Key Features
- **No Database Query**: All data extracted from JWT token claims
- **Fast Response**: Minimal processing required
- **Current User Only**: Returns data for the authenticated user
- **Role Information**: Includes user's role for authorization checks
- **Tenant Context**: Includes tenant ID for multi-tenant operations
## Frontend Implementation
### MeService (`src/services/API/MeService.js`)
Handles direct communication with the backend Me API and provides utility methods for user profile operations.
**Key Features:**
- GET request with automatic JWT authentication
- Response transformation (snake_case to camelCase)
- User-friendly error message mapping
- Helper methods for common use cases
- Automatic token refresh integration
**Methods:**
#### `getMe()`
Main method to fetch the current user's profile.
```javascript
import MeService from './services/API/MeService';
const profile = await MeService.getMe();
console.log(profile);
// Output:
// {
// userId: "550e8400-e29b-41d4-a716-446655440000",
// email: "john@example.com",
// name: "John Doe",
// role: "owner",
// tenantId: "650e8400-e29b-41d4-a716-446655440000"
// }
```
**Returns:**
```javascript
{
userId: string, // User's unique identifier (UUID)
email: string, // User's email address
name: string, // User's full name
role: string, // User's role (e.g., "owner", "admin", "user")
tenantId: string // User's tenant/organization ID (UUID)
}
```
**Throws:**
- "Authentication required. Please log in to continue." - If JWT token is missing/invalid
- "Invalid or expired authentication token. Please log in again." - If token has expired
- Generic error message for other failures
#### `hasRole(requiredRole)`
Check if the current user has a specific role.
```javascript
const isOwner = await MeService.hasRole("owner");
if (isOwner) {
console.log("User is an owner");
}
const isAdmin = await MeService.hasRole("admin");
```
**Parameters:**
- `requiredRole` (string): Role to check for (e.g., "owner", "admin", "user")
**Returns:** `boolean` - True if user has the required role
#### `getTenantId()`
Get the current user's tenant ID.
```javascript
const tenantId = await MeService.getTenantId();
console.log("User's tenant:", tenantId);
```
**Returns:** `string|null` - Tenant ID or null if not available
#### `isCurrentUser(userId)`
Verify that the current user matches a specific user ID.
```javascript
const isSameUser = await MeService.isCurrentUser("550e8400-...");
if (isSameUser) {
console.log("This is the current user");
}
```
**Parameters:**
- `userId` (string): User ID to verify against
**Returns:** `boolean` - True if the current user matches the provided ID
## Data Flow
```
User is authenticated (has JWT token)
↓
Component calls MeService.getMe()
↓
ApiClient.get("/api/v1/me")
↓
Token automatically refreshed if needed (ApiClient feature)
↓
GET /api/v1/me with Authorization header
↓
Backend JWT middleware extracts user from token
↓
Backend returns user profile from JWT claims
↓
MeService transforms response (snake_case → camelCase)
↓
Component receives user profile
```
## Error Handling
### Error Types
| Error Condition | Response | Frontend Behavior |
|----------------|----------|-------------------|
| Missing JWT token | 401 Unauthorized | "Authentication required. Please log in to continue." |
| Invalid JWT token | 401 Unauthorized | "Invalid or expired authentication token." |
| Expired JWT token | 401 Unauthorized | Token auto-refresh triggered, then retry |
| Server error | 500 Internal Server Error | Generic error message |
### Error Message Mapping
```javascript
// Backend error → Frontend error
"unauthorized" → "Authentication required. Please log in to continue."
"token" → "Invalid or expired authentication token. Please log in again."
Other errors → Original error message or generic fallback
```
## Security Features
1. **JWT Authentication Required**: Endpoint requires valid JWT token
2. **Token Validation**: Backend validates token signature and expiration
3. **No PII in Logs**: Backend uses hashed/redacted email in logs (CWE-532)
4. **Automatic Token Refresh**: ApiClient ensures token is valid before request
5. **Context Extraction**: User data extracted from secure JWT context
## Usage Examples
### Display User Profile in Dashboard
```javascript
import React, { useEffect, useState } from 'react';
import MeService from '../../services/API/MeService';
function UserProfile() {
const [profile, setProfile] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
const fetchProfile = async () => {
try {
const data = await MeService.getMe();
setProfile(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchProfile();
}, []);
if (loading) return
);
}
export default AdminPanel;
```
### Sync Profile with AuthManager
```javascript
import { useAuth } from '../../services/Services';
import MeService from '../../services/API/MeService';
function Dashboard() {
const { authManager } = useAuth();
useEffect(() => {
const syncProfile = async () => {
try {
// Get fresh profile from backend
const profile = await MeService.getMe();
// Get stored profile from AuthManager
const storedUser = authManager.getUser();
// Check if data is in sync
if (storedUser.email !== profile.email) {
console.warn("Profile data mismatch detected");
// Could update AuthManager or prompt user
}
console.log("Profile synced successfully");
} catch (error) {
console.error("Failed to sync profile:", error);
}
};
syncProfile();
}, [authManager]);
return
Dashboard
;
}
```
### Check Tenant Context
```javascript
import MeService from '../../services/API/MeService';
async function verifyTenantAccess(requiredTenantId) {
const userTenantId = await MeService.getTenantId();
if (userTenantId !== requiredTenantId) {
throw new Error("Access denied. Wrong tenant context.");
}
console.log("Tenant access verified");
return true;
}
// Usage
try {
await verifyTenantAccess("650e8400-e29b-41d4-a716-446655440000");
// Proceed with tenant-specific operations
} catch (error) {
console.error(error.message);
// Redirect or show error
}
```
### Verify Current User
```javascript
import MeService from '../../services/API/MeService';
function EditProfileButton({ profileOwnerId }) {
const [canEdit, setCanEdit] = useState(false);
useEffect(() => {
const checkOwnership = async () => {
const isOwner = await MeService.isCurrentUser(profileOwnerId);
setCanEdit(isOwner);
};
checkOwnership();
}, [profileOwnerId]);
if (!canEdit) return null;
return ;
}
```
## Testing the Me API
### 1. Prerequisites
- Backend running at `http://localhost:8000`
- Frontend running at `http://localhost:5173`
- User logged in (valid JWT token)
### 2. Testing via Dashboard
Add a profile component to the dashboard:
```javascript
// In Dashboard.jsx
import MeService from '../../services/API/MeService';
const [userProfile, setUserProfile] = useState(null);
useEffect(() => {
const loadProfile = async () => {
try {
const profile = await MeService.getMe();
setUserProfile(profile);
console.log("Profile loaded:", profile);
} catch (error) {
console.error("Failed to load profile:", error);
}
};
loadProfile();
}, []);
// In JSX
{userProfile && (
{userProfile.name}
{userProfile.email}
Role: {userProfile.role}
)}
```
### 3. Testing via Browser Console
```javascript
// After logging in, open browser console
// Test basic profile fetch
const profile = await MeService.getMe();
console.log(profile);
// Expected: User profile object
// Test role check
const isOwner = await MeService.hasRole("owner");
console.log("Is owner:", isOwner);
// Test tenant ID
const tenantId = await MeService.getTenantId();
console.log("Tenant ID:", tenantId);
// Test user verification
const isCurrent = await MeService.isCurrentUser(profile.userId);
console.log("Is current user:", isCurrent); // Should be true
```
### 4. Testing with curl
```bash
# 1. Login and get access token
ACCESS_TOKEN=$(curl -X POST http://localhost:8000/api/v1/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "SecurePass123!"
}' | jq -r '.access_token')
# 2. Test me endpoint
curl -X GET http://localhost:8000/api/v1/me \
-H "Authorization: JWT $ACCESS_TOKEN" | jq
# Expected output:
# {
# "user_id": "550e8400-...",
# "email": "test@example.com",
# "name": "Test User",
# "role": "owner",
# "tenant_id": "650e8400-..."
# }
# 3. Test without authentication (should fail)
curl -X GET http://localhost:8000/api/v1/me | jq
# Expected: 401 Unauthorized
# 4. Test with invalid token (should fail)
curl -X GET http://localhost:8000/api/v1/me \
-H "Authorization: JWT invalid_token" | jq
# Expected: 401 Unauthorized
```
### 5. Testing Token Refresh Integration
```javascript
// Test that token refresh works automatically
// 1. Login
// 2. Manually set token expiry to 30 seconds from now
const newExpiry = new Date(Date.now() + 30000).toISOString();
localStorage.setItem('maplepress_access_expiry', newExpiry);
// 3. Wait 31 seconds, then call getMe
await new Promise(resolve => setTimeout(resolve, 31000));
const profile = await MeService.getMe();
// 4. Check console logs - should see automatic token refresh
// 5. Response should be successful despite expired token
console.log("Profile after token refresh:", profile);
```
### 6. Testing Against Stored Data
```javascript
// Compare Me API response with stored AuthManager data
import { useAuth } from './services/Services';
import MeService from './services/API/MeService';
const { authManager } = useAuth();
// Get stored user
const storedUser = authManager.getUser();
console.log("Stored user:", storedUser);
// Get fresh profile from API
const profile = await MeService.getMe();
console.log("API profile:", profile);
// Compare
console.log("Email match:", storedUser.email === profile.email);
console.log("Name match:", storedUser.name === profile.name);
console.log("Role match:", storedUser.role === profile.role);
```
## Integration with Existing Services
The MeService automatically integrates with existing infrastructure:
### Authentication (AuthManager)
```javascript
// MeService uses ApiClient, which automatically:
// - Adds JWT token to Authorization header
// - Refreshes token if expired
// - Handles 401 errors
```
### Token Refresh (RefreshTokenService)
```javascript
// Automatic token refresh before getMe request
// If token expires within 1 minute, it's refreshed proactively
// If 401 received, token is refreshed and request is retried
```
### Data Consistency
```javascript
// Me API returns data from JWT token
// AuthManager stores data from login/register/refresh
// Both should be in sync, but Me API is source of truth
```
## Use Cases
### 1. Dashboard User Display
Display the current user's name and email in the dashboard header.
```javascript
const profile = await MeService.getMe();
setDashboardHeader(profile.name, profile.email);
```
### 2. Role-Based UI
Show or hide UI elements based on user role.
```javascript
const isAdmin = await MeService.hasRole("owner");
setShowAdminPanel(isAdmin);
```
### 3. Tenant Context
Ensure user is operating in the correct tenant context.
```javascript
const tenantId = await MeService.getTenantId();
loadTenantSpecificData(tenantId);
```
### 4. Profile Verification
Verify that the current user matches a resource owner.
```javascript
const canEdit = await MeService.isCurrentUser(resourceOwnerId);
```
### 5. Session Validation
Periodically verify that the session is still valid.
```javascript
setInterval(async () => {
try {
await MeService.getMe();
console.log("Session still valid");
} catch (error) {
console.log("Session expired, logging out");
authManager.logout();
}
}, 5 * 60 * 1000); // Every 5 minutes
```
## Data Comparison: JWT vs Stored
### JWT Token Data (from Me API)
- **Source**: JWT token claims
- **Updated**: On login, registration, or token refresh
- **Authoritative**: Yes (always current)
- **Requires Network**: Yes
### AuthManager Stored Data
- **Source**: localStorage (from login/register/refresh responses)
- **Updated**: On login, registration, or token refresh
- **Authoritative**: No (could be stale)
- **Requires Network**: No
**Recommendation**: Use Me API when you need authoritative current data, use AuthManager for quick access without network calls.
## Troubleshooting
### "Authentication required" error
**Possible causes:**
1. User not logged in
2. Access token expired
3. Refresh token expired
4. Session invalidated
**Solution:**
- Check `authManager.isAuthenticated()` before calling
- Login again if session expired
- Check browser console for token refresh logs
### Profile data doesn't match stored data
**Possible causes:**
1. Token was refreshed but AuthManager not updated
2. Profile was updated on another device
3. Data corruption in localStorage
**Solution:**
- Me API is source of truth - use its data
- Update AuthManager with fresh data if needed
- Clear localStorage and login again if corrupted
### Request succeeds but returns empty fields
**Possible causes:**
1. JWT token missing required claims
2. Backend not setting context values correctly
3. Token format incorrect
**Solution:**
- Check token structure (decode JWT at jwt.io)
- Verify backend is setting all required claims
- Re-login to get fresh token
### 401 error despite valid token
**Possible causes:**
1. Clock skew between client and server
2. Token signature invalid
3. Token expired but not detected
**Solution:**
- Check system clock synchronization
- Clear tokens and login again
- Verify token expiry dates in localStorage
## Related Files
### Created Files
```
src/services/API/MeService.js
docs/ME_API.md
```
### Backend Reference Files
```
cloud/maplepress-backend/docs/API.md (lines 374-413)
cloud/maplepress-backend/internal/interface/http/handler/gateway/me_handler.go
```
## Related Documentation
- [LOGIN_API.md](./LOGIN_API.md) - Login and JWT token acquisition
- [REFRESH_TOKEN_API.md](./REFRESH_TOKEN_API.md) - Automatic token refresh
- [HELLO_API.md](./HELLO_API.md) - Simple authenticated endpoint testing
- [FRONTEND_ARCHITECTURE.md](./FRONTEND_ARCHITECTURE.md) - Architecture overview
- [README.md](./README.md) - Documentation index
- [Backend API Documentation](../../../cloud/maplepress-backend/docs/API.md) - Complete API reference
## Summary
The Me API implementation provides:
1. **User Profile Access**: Get current user's profile from JWT token
2. **Fast & Efficient**: No database queries, data from token claims
3. **Helper Methods**: Role checking, tenant ID, user verification
4. **Automatic Integration**: Works seamlessly with token refresh
5. **Error Handling**: Clear error messages and graceful failures
6. **Use Case Focused**: Methods designed for common scenarios
This endpoint is essential for displaying user information in the dashboard and implementing role-based access control.
---
**Last Updated**: October 30, 2024
**Frontend Version**: 0.0.0
**Documentation Version**: 1.0.0