Intercode SaaS Kit
  • Welcome to SaaS Starter Kit
  • Getting Started
    • Technology stack
    • Database Setup
    • Local Environment Setup
  • Basics
    • Dependencies
    • App architecture
    • Deployment
    • App roles
    • Endpoints List
      • Auth
      • Two Factor Auth
      • Businesses
      • Demo
      • Email
      • Export Document
      • Email Files
      • Files Demo
      • Leads
      • Orders
      • Payments
      • Subscriptions
      • Teams
      • Team Memberships
      • User Admin
  • Animation and Styles
    • Framer Motion
    • Ant Design and Styles
  • Pages
    • Auth
      • Working with PassportJS
      • Two-Factor Auth
      • OAuth Providers
    • Leads
    • Businesses
    • Team management
      • Ownership
    • Profile
    • User Settings
      • App Tour
    • App Settings
      • Lead Statuses
    • Dashboard
      • Lead volume widget
      • Doughnut chart widget
      • Recent leads table widget
      • Lead count over period widget
    • Demo
  • Features
    • Impersonation
    • Subscriptions (Stripe)
    • Search
    • Sentry
    • Captcha
    • Audit Logs
    • Internationalization
  • External integrations
    • Mailer
    • Google oAuth2
    • Facebook oAuth2
    • S3 compatible storage (AWS, MinIO)
Powered by GitBook
On this page
  • Functionality
  • Auth Provider
  • Components
  • Auth Flow Schema
  1. Pages

Auth

This document provides a detailed overview of the Intecode Common Auth Module, which is responsible for handling user authentication and authorization.

Functionality

The Auth module ensures secure access to the system by managing user login, registration, password recovery, and session handling. It includes role-based access controls and integrates with OAuth providers.

Auth Provider

The AuthProvider component and AuthContext provide authentication management and user context for the application. It handles storing and managing authentication tokens, user information, and team-related updates.

Key Features:

1. State Management:

  • token & refreshToken: Stored in localStorage using the useLocalStorage hook. These hold the access token and refresh token for authenticated sessions.

  • currentUser: Holds the currently authenticated user's data.

2. Functions:

  • fetchUser:

    • Fetches the current user data from the API using userService.getUser().

    • Updates user roles and checks if the user has a team. If no team exists, it navigates to the team creation page.

  • setAuth: Stores the access token and refresh token in localStorage.

  • logout:

    • Logs the user out by deactivating their membership (if applicable) and clears the authentication tokens.

    • Redirects to the sign-in page.

  • changeTeam:

    • Switches the active team by calling the authService.changeTeam API and updating tokens.

    • Re-fetches the user data after changing the team.

3. Context Usage:

  • AuthContext.Provider: Provides the authentication-related values and functions (token, setAuth, logout, etc.) to the component tree.

  • useAuth: Hook to access the AuthContext values and functions within any component.

Usage Example:

To access the authentication context:

const { token, logout, fetchUser, currentUser } = useAuth();

This structure allows centralized control of authentication, token management, and user/team updates throughout the app.

AuthContext.js
import { createContext, useCallback, useContext, useState } from 'react';
import { useLocalStorage } from '../common/hooks/useLocalStorage';
import { useNavigate } from 'react-router-dom';
import { useUserService } from '../services/userService';
import { REFRESH_TOKEN, TOKEN, USER_ROLES } from '../common/constants/localStorage';
import { updateToken } from '../services/tokenService';
import { useAuthService } from '../services/authService';
import globalNotification from '../common/hooks/globalNotification';
import { useTeamService } from '../services/teamService';

export const AuthContext = createContext(null);

export const AuthProvider = ({ children }) => {
    const [token, setToken] = useLocalStorage(TOKEN, null);
    const [refreshToken, setRefreshToken] = useLocalStorage(REFRESH_TOKEN, null);
    const [currentUser, setCurrentUser] = useState();
    const [isUserLoading, setIsUserLoading] = useState(true);

    const navigate = useNavigate();
    const userService = useUserService();
    const authService = useAuthService();
    const { updateMembership, findUserMembership } = useTeamService();

    const fetchUser = useCallback(async () => {
        try {
            if (token) {
                setIsUserLoading(true);
                const { data } = await userService.getUser();

                setCurrentUser(data);
                updateToken(USER_ROLES, data.userRoleNames);
                setTimeout(() => {
                    setIsUserLoading(false);
                }, 1000);

                if (!data.teamId) {
                    navigate('/create-team');
                }
            }
        } catch (e) {
            logout();
            console.error(e);
        }
    }, []);

    const setAuth = (token, refreshToken) => {
        setToken(token);
        setRefreshToken(refreshToken);
    };

    const logout = async () => {
        try {
            if (!currentUser) {
                throw new Error();
            }

            const { data } = await findUserMembership(currentUser.id);

            if (!data) {
                throw new Error();
            }

            await updateMembership({ id: data.id, isActive: false });
        } catch (e) {
            console.log(e);
        } finally {
            updateToken(TOKEN, null);
            updateToken(REFRESH_TOKEN, null);
            updateToken(USER_ROLES, null);
            navigate('/auth/sign-in');
        }
    };

    const changeTeam = async teamId => {
        try {
            const { data } = await authService.changeTeam(teamId);

            setAuth(data.accessToken, data.refreshToken || data.accessToken);
        } catch (error) {
            globalNotification.open({ message: 'This membership is not valid' });
        } finally {
            await fetchUser();
        }
    };

    return (
        <AuthContext.Provider
            value={{
                token,
                setAuth,
                logout,
                fetchUser,
                currentUser,
                isUserLoading,
                changeTeam,
            }}
        >
            {children}
        </AuthContext.Provider>
    );
};

export const useAuth = () => {
    return useContext(AuthContext);
};

Components

Components Necessary for Optimal Functioning of the Auth Module

Auth Guard

The AuthGuard component is a route protection mechanism that checks if a user is authenticated before granting access to specific routes.

Key Features:

  1. Authentication Check:

    • It verifies if the user is on an "auth" route (e.g., /auth/sign-in) by checking the URL path.

    • If the user is already authenticated (i.e., has valid tokens stored), they are redirected to the dashboard route.

  2. Route Protection:

    • If the user is accessing a non-auth route and doesn't have valid tokens (TOKEN and REFRESH_TOKEN in localStorage), they are redirected to the sign-in page (auth/sign-in).

import { useLocation } from 'react-router-dom';
import { Navigate } from 'react-router-dom';
import { routesPath } from '../../common/constants/routesPath';
import { TOKEN, REFRESH_TOKEN } from '../../common/constants/localStorage';

function AuthGuard({ children }) {
    const location = useLocation();
    const isAuth = location.pathname.split('/')[1] === 'auth';
    if (isAuth) {
        return getToken(TOKEN) && getToken(REFRESH_TOKEN) ? <Navigate to={routesPath.ROOT.DASHBOARD} /> : children;
    } else {
        return getToken(TOKEN) && getToken(REFRESH_TOKEN) ? children : <Navigate to={'auth/sign-in'} />;
    }
}

const getToken = tokenName => {
    const token = localStorage.getItem(tokenName);

    if (token === null || token === 'undefined') {
        return null;
    }
    return JSON.parse(localStorage.getItem(tokenName));
};

export default AuthGuard;

GetUser Decorator

This decorator retrieves user data parsed by JwtAuthenticationGuard

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { JwtUserPayload } from 'src/common/interfaces/jwt-user-payload.interface';

export const GetUser = createParamDecorator((data, ctx: ExecutionContext): JwtUserPayload => {
    const req = ctx.switchToHttp().getRequest();
    return req.user;
});

Auth Flow Schema

PreviousAnt Design and StylesNextWorking with PassportJS

Last updated 6 months ago

Auth Flow Block Schema