Spaces:
Sleeping
Sleeping
| # Getting Started with SwiftOps Authentication | |
| **For Frontend Developers** | Estimated Time: 30 minutes | |
| --- | |
| ## π Welcome! | |
| This guide will help you integrate SwiftOps authentication into your frontend application. By the end, you'll have: | |
| - β Working login/logout functionality | |
| - β User profile display | |
| - β Protected routes | |
| - β Error handling | |
| --- | |
| ## π Documentation Overview | |
| You have access to three documents: | |
| 1. **This Guide (Getting Started)** - Start here (30 min read) | |
| 2. **Quick Reference** - For quick lookups later | |
| 3. **Complete API Guide** - For deep dives and advanced features | |
| **Read them in order:** Getting Started β Quick Reference β Complete Guide | |
| --- | |
| ## π― What You'll Build | |
| ### Step 1: Login Page | |
| User enters email/password β System returns token β Store token β Redirect to dashboard | |
| ### Step 2: Dashboard | |
| Check if token exists β Fetch user profile β Display user info | |
| ### Step 3: Protected Routes | |
| Before showing any page β Check token β If invalid, redirect to login | |
| ### Step 4: Logout | |
| User clicks logout β Call logout API β Clear token β Redirect to login | |
| --- | |
| ## π Let's Start! | |
| ### Prerequisites | |
| - Basic JavaScript/React knowledge | |
| - Fetch API or Axios experience | |
| - Understanding of localStorage | |
| --- | |
| ## Step 1: Create Auth Service (10 minutes) | |
| Create a file: `src/services/auth.js` | |
| ```javascript | |
| // auth.js - Your authentication service | |
| const API_BASE = 'https://your-api-domain.com/api/v1'; | |
| const TOKEN_KEY = 'swiftops_token'; | |
| class AuthService { | |
| // Get token from localStorage | |
| getToken() { | |
| return localStorage.getItem(TOKEN_KEY); | |
| } | |
| // Save token to localStorage | |
| setToken(token) { | |
| localStorage.setItem(TOKEN_KEY, token); | |
| } | |
| // Remove token from localStorage | |
| clearToken() { | |
| localStorage.removeItem(TOKEN_KEY); | |
| } | |
| // Check if user is logged in | |
| isAuthenticated() { | |
| return !!this.getToken(); | |
| } | |
| // Get headers for authenticated requests | |
| getHeaders() { | |
| const token = this.getToken(); | |
| return { | |
| 'Content-Type': 'application/json', | |
| ...(token && { 'Authorization': `Bearer ${token}` }) | |
| }; | |
| } | |
| // Login user | |
| async login(email, password) { | |
| const response = await fetch(`${API_BASE}/auth/login`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email, password }) | |
| }); | |
| if (!response.ok) { | |
| const error = await response.json(); | |
| throw new Error(error.detail || 'Login failed'); | |
| } | |
| const data = await response.json(); | |
| this.setToken(data.access_token); | |
| return data.user; | |
| } | |
| // Get current user profile | |
| async getCurrentUser() { | |
| const response = await fetch(`${API_BASE}/auth/me`, { | |
| method: 'GET', | |
| headers: this.getHeaders() | |
| }); | |
| if (!response.ok) { | |
| if (response.status === 401) { | |
| // Token expired or invalid | |
| this.clearToken(); | |
| } | |
| throw new Error('Failed to fetch user profile'); | |
| } | |
| return await response.json(); | |
| } | |
| // Logout user | |
| async logout() { | |
| try { | |
| // Call logout endpoint for audit | |
| await fetch(`${API_BASE}/auth/logout`, { | |
| method: 'POST', | |
| headers: this.getHeaders() | |
| }); | |
| } catch (error) { | |
| console.error('Logout error:', error); | |
| } finally { | |
| // Always clear token | |
| this.clearToken(); | |
| } | |
| } | |
| } | |
| // Export singleton instance | |
| export default new AuthService(); | |
| ``` | |
| **What this does:** | |
| - Stores/retrieves JWT token in localStorage | |
| - Provides methods for login, logout, and getting user | |
| - Handles token expiration automatically | |
| --- | |
| ## Step 2: Create Login Page (15 minutes) | |
| Create a file: `src/pages/LoginPage.jsx` | |
| ```javascript | |
| import React, { useState } from 'react'; | |
| import { useNavigate } from 'react-router-dom'; | |
| import AuthService from '../services/auth'; | |
| function LoginPage() { | |
| const [email, setEmail] = useState(''); | |
| const [password, setPassword] = useState(''); | |
| const [error, setError] = useState(''); | |
| const [loading, setLoading] = useState(false); | |
| const navigate = useNavigate(); | |
| const handleSubmit = async (e) => { | |
| e.preventDefault(); | |
| setError(''); | |
| setLoading(true); | |
| try { | |
| // Call login API | |
| const user = await AuthService.login(email, password); | |
| // Success! Redirect to dashboard | |
| console.log('Logged in as:', user.email); | |
| navigate('/dashboard'); | |
| } catch (err) { | |
| // Show error to user | |
| setError(err.message); | |
| setLoading(false); | |
| } | |
| }; | |
| return ( | |
| <div className="login-container"> | |
| <div className="login-box"> | |
| <h1>Login to SwiftOps</h1> | |
| {error && ( | |
| <div className="error-message"> | |
| {error} | |
| </div> | |
| )} | |
| <form onSubmit={handleSubmit}> | |
| <div className="form-group"> | |
| <label>Email</label> | |
| <input | |
| type="email" | |
| value={email} | |
| onChange={(e) => setEmail(e.target.value)} | |
| placeholder="user@example.com" | |
| required | |
| disabled={loading} | |
| /> | |
| </div> | |
| <div className="form-group"> | |
| <label>Password</label> | |
| <input | |
| type="password" | |
| value={password} | |
| onChange={(e) => setPassword(e.target.value)} | |
| placeholder="Enter your password" | |
| required | |
| disabled={loading} | |
| /> | |
| </div> | |
| <button | |
| type="submit" | |
| className="btn-primary" | |
| disabled={loading} | |
| > | |
| {loading ? 'Logging in...' : 'Login'} | |
| </button> | |
| </form> | |
| <div className="login-footer"> | |
| <a href="/forgot-password">Forgot password?</a> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default LoginPage; | |
| ``` | |
| **CSS (optional):** | |
| ```css | |
| .login-container { | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| background: #f5f5f5; | |
| } | |
| .login-box { | |
| background: white; | |
| padding: 40px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
| width: 100%; | |
| max-width: 400px; | |
| } | |
| .error-message { | |
| background: #fee; | |
| color: #c00; | |
| padding: 12px; | |
| border-radius: 4px; | |
| margin-bottom: 20px; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| } | |
| .form-group input { | |
| width: 100%; | |
| padding: 10px; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| font-size: 16px; | |
| } | |
| .btn-primary { | |
| width: 100%; | |
| padding: 12px; | |
| background: #007bff; | |
| color: white; | |
| border: none; | |
| border-radius: 4px; | |
| font-size: 16px; | |
| cursor: pointer; | |
| } | |
| .btn-primary:hover { | |
| background: #0056b3; | |
| } | |
| .btn-primary:disabled { | |
| background: #ccc; | |
| cursor: not-allowed; | |
| } | |
| .login-footer { | |
| margin-top: 20px; | |
| text-align: center; | |
| } | |
| ``` | |
| --- | |
| ## Step 3: Create Dashboard (5 minutes) | |
| Create a file: `src/pages/Dashboard.jsx` | |
| ```javascript | |
| import React, { useState, useEffect } from 'react'; | |
| import { useNavigate } from 'react-router-dom'; | |
| import AuthService from '../services/auth'; | |
| function Dashboard() { | |
| const [user, setUser] = useState(null); | |
| const [loading, setLoading] = useState(true); | |
| const [error, setError] = useState(null); | |
| const navigate = useNavigate(); | |
| useEffect(() => { | |
| loadUser(); | |
| }, []); | |
| const loadUser = async () => { | |
| try { | |
| const userData = await AuthService.getCurrentUser(); | |
| setUser(userData); | |
| } catch (err) { | |
| setError(err.message); | |
| // If token invalid, redirect to login | |
| navigate('/login'); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| const handleLogout = async () => { | |
| await AuthService.logout(); | |
| navigate('/login'); | |
| }; | |
| if (loading) { | |
| return <div>Loading...</div>; | |
| } | |
| if (error) { | |
| return <div>Error: {error}</div>; | |
| } | |
| return ( | |
| <div className="dashboard"> | |
| <header> | |
| <h1>Welcome, {user.name}!</h1> | |
| <button onClick={handleLogout}>Logout</button> | |
| </header> | |
| <div className="user-info"> | |
| <h2>Your Profile</h2> | |
| <p><strong>Email:</strong> {user.email}</p> | |
| <p><strong>Role:</strong> {user.role}</p> | |
| <p><strong>Phone:</strong> {user.phone || 'Not set'}</p> | |
| <p><strong>Status:</strong> {user.status}</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| export default Dashboard; | |
| ``` | |
| --- | |
| ## Step 4: Set Up Routes (5 minutes) | |
| Update your `src/App.jsx`: | |
| ```javascript | |
| import React from 'react'; | |
| import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; | |
| import LoginPage from './pages/LoginPage'; | |
| import Dashboard from './pages/Dashboard'; | |
| import AuthService from './services/auth'; | |
| // Protected Route component | |
| function ProtectedRoute({ children }) { | |
| if (!AuthService.isAuthenticated()) { | |
| return <Navigate to="/login" replace />; | |
| } | |
| return children; | |
| } | |
| function App() { | |
| return ( | |
| <BrowserRouter> | |
| <Routes> | |
| <Route path="/login" element={<LoginPage />} /> | |
| <Route | |
| path="/dashboard" | |
| element={ | |
| <ProtectedRoute> | |
| <Dashboard /> | |
| </ProtectedRoute> | |
| } | |
| /> | |
| <Route path="/" element={<Navigate to="/dashboard" replace />} /> | |
| </Routes> | |
| </BrowserRouter> | |
| ); | |
| } | |
| export default App; | |
| ``` | |
| --- | |
| ## π You're Done! Test It Out | |
| ### Test Login | |
| 1. Start your dev server: `npm start` | |
| 2. Go to `http://localhost:3000/login` | |
| 3. Enter credentials: | |
| - Email: `user@example.com` | |
| - Password: `SecurePass123!` | |
| 4. Click "Login" | |
| 5. Should redirect to dashboard | |
| ### Test Protected Route | |
| 1. Go to `http://localhost:3000/dashboard` without logging in | |
| 2. Should redirect to `/login` | |
| ### Test Logout | |
| 1. On dashboard, click "Logout" | |
| 2. Should redirect to `/login` | |
| 3. Try accessing `/dashboard` again | |
| 4. Should redirect to `/login` | |
| --- | |
| ## π Common Issues & Solutions | |
| ### Issue: "Login failed" | |
| **Solution:** Check if API URL is correct. Verify email/password. | |
| ### Issue: "Failed to fetch user profile" | |
| **Solution:** Token might be expired. Clear localStorage and login again. | |
| ### Issue: Infinite redirect loop | |
| **Solution:** Check if token is being stored correctly in localStorage. | |
| ### Issue: CORS error | |
| **Solution:** Backend needs to allow your frontend domain in CORS settings. | |
| --- | |
| ## π What You Just Built | |
| β **Login System** - Users can login with email/password | |
| β **Token Management** - JWT token stored in localStorage | |
| β **Protected Routes** - Dashboard only accessible when logged in | |
| β **Auto-Logout** - Expired tokens handled automatically | |
| β **User Profile** - Display current user information | |
| β **Logout** - Users can logout | |
| --- | |
| ## π Next Steps | |
| ### Phase 2: Profile Management | |
| Add ability to update user profile: | |
| ```javascript | |
| // In Dashboard.jsx | |
| const updateProfile = async () => { | |
| const response = await fetch(`${API_BASE}/auth/me`, { | |
| method: 'PUT', | |
| headers: AuthService.getHeaders(), | |
| body: JSON.stringify({ | |
| first_name: 'Jane', | |
| phone: '+254712345678' | |
| }) | |
| }); | |
| const updatedUser = await response.json(); | |
| setUser(updatedUser); | |
| }; | |
| ``` | |
| ### Phase 3: Invitation System | |
| Handle invitation links when users are invited: | |
| ```javascript | |
| // InvitationAcceptPage.jsx | |
| const acceptInvitation = async (token, password, name) => { | |
| const response = await fetch(`${API_BASE}/invitations/accept`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| token, | |
| password, | |
| name, | |
| accept_terms: true | |
| }) | |
| }); | |
| const data = await response.json(); | |
| AuthService.setToken(data.access_token); | |
| navigate('/dashboard'); | |
| }; | |
| ``` | |
| ### Phase 4: Password Reset | |
| Add forgot password and reset password flows. | |
| --- | |
| ## π Where to Go Next | |
| 1. **Quick Reference** - For quick endpoint lookups | |
| 2. **Complete API Guide** - For advanced features: | |
| - Password management | |
| - Invitation system | |
| - Error handling patterns | |
| - Security best practices | |
| --- | |
| ## π¬ Need Help? | |
| ### Common Questions | |
| **Q: How long does the token last?** | |
| A: 24 hours. After that, user must login again. | |
| **Q: Can I use axios instead of fetch?** | |
| A: Yes! Just replace fetch calls with axios. Example: | |
| ```javascript | |
| const response = await axios.post(`${API_BASE}/auth/login`, { | |
| email, password | |
| }); | |
| const { access_token, user } = response.data; | |
| ``` | |
| **Q: How do I show loading spinners?** | |
| A: Use the `loading` state variable like in the examples. | |
| **Q: What if I need to call other APIs?** | |
| A: Use `AuthService.getHeaders()` to include the auth token: | |
| ```javascript | |
| fetch(`${API_BASE}/users`, { | |
| headers: AuthService.getHeaders() | |
| }); | |
| ``` | |
| **Q: Mobile app development?** | |
| A: Use SecureStore instead of localStorage: | |
| ```javascript | |
| import * as SecureStore from 'expo-secure-store'; | |
| await SecureStore.setItemAsync('token', token); | |
| ``` | |
| --- | |
| ## β Checklist | |
| Before moving to production: | |
| - [ ] Login page works | |
| - [ ] Dashboard shows user info | |
| - [ ] Logout works | |
| - [ ] Protected routes redirect to login | |
| - [ ] Error messages display properly | |
| - [ ] Token stored in localStorage | |
| - [ ] 401 errors handled (auto-logout) | |
| - [ ] Loading states shown | |
| - [ ] Password validation on client side | |
| --- | |
| ## π― Summary | |
| **What you learned:** | |
| - How to integrate SwiftOps authentication | |
| - Token-based authentication flow | |
| - Protected routes implementation | |
| - Error handling patterns | |
| **Time invested:** 30-60 minutes | |
| **Result:** Working auth system | |
| **Next steps:** Read the Quick Reference for other features like password reset, invitation system, and profile management. | |
| --- | |
| **Good luck with your implementation! π** | |
| Need detailed information? See `AUTH_API_COMPLETE.md` | |
| Need quick lookup? See `AUTH_API_QUICK_REFERENCE.md` | |