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

// 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

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):

.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

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:

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:

// 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:

// 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:

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:

fetch(`${API_BASE}/users`, {
  headers: AuthService.getHeaders()
});

Q: Mobile app development?
A: Use SecureStore instead of localStorage:

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