swiftops-backend / docs /api /auth /GETTING_STARTED_AUTH.md
kamau1's picture
feat(auth): fix platform admin registration flow and update OTP/registration schemas
ab3ba46
# 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`