appwrite auth workflow

Complete Authentication Workflow Documentation

Overview

This React blog application uses Appwrite as the Backend-as-a-Service (BaaS) for authentication, combined with Redux Toolkit for client-side state management. The authentication system provides user registration, login, logout, session persistence, and comprehensive route protection across the entire application.

Architecture Components

1. Authentication Service Layer (appwrite/auth.js)

The AuthService class acts as the main interface between the application and Appwrite's authentication API.

Class Structure:


export class AuthService {

    client = new Client();

    account;

    constructor() {

        this.client

            .setEndpoint(conf.appwriteUrl)

            .setProject(conf.appwriteProjectId);

        this.account = new Account(this.client);

    }

}

Key Methods:

Session Management:

2. Redux State Management (src/store/authSlice.js & src/store/store.js)

Manages authentication state throughout the application using Redux Toolkit.

Store Configuration (src/store/store.js):


import {configureStore} from '@reduxjs/toolkit';

import authSlice from './authSlice';

  

const store = configureStore({

    reducer: {

        auth: authSlice

    }

});

State Structure (src/store/authSlice.js):


const initialState = {

    status: false,    // true if authenticated, false if not

    userData: null    // user information from Appwrite

}

Actions:

3. Configuration (src/conf/conf.js)

Centralizes environment variables for Appwrite connection:


const conf = {

    appwriteUrl: String(import.meta.env.VITE_APPWRITE_URL),

    appwriteProjectId: String(import.meta.env.VITE_APPWRITE_PROJECT_ID),

    appwriteDatabaseId: String(import.meta.env.VITE_APPWRITE_DATABASE_ID),

    appwriteCollectionId: String(import.meta.env.VITE_APPWRITE_COLLECTION_ID),

    appwriteBucketId: String(import.meta.env.VITE_APPWRITE_BUCKET_ID),

    tinyMce: String(import.meta.env.VITE_TINYMCE_API_KEY)

}

4. Application Entry Point (src/main.jsx)

Sets up the complete routing structure with authentication protection using React Router and Redux Provider.

Complete Router Configuration:


const router = createBrowserRouter([

  {

    path: "/",

    element: <App />,

    children: [

        { path: "/", element: <Home /> },

        {

            path: "/login",

            element: (

                <AuthLayout authentication={false}>

                    <Login />

                </AuthLayout>

            ),

        },

        {

            path: "/signup",

            element: (

                <AuthLayout authentication={false}>

                    <Signup />

                </AuthLayout>

            ),

        },

        {

            path: "/all-posts",

            element: (

                <AuthLayout authentication>

                    <AllPosts />

                </AuthLayout>

            ),

        },

        {

            path: "/add-post",

            element: (

                <AuthLayout authentication>

                    <AddPost />

                </AuthLayout>

            ),

        },

        {

            path: "/edit-post/:slug",

            element: (

                <AuthLayout authentication>

                    <EditPost />

                </AuthLayout>

            ),

        },

        { path: "/post/:slug", element: <Post /> },

    ],

},

])

Route Categories:

5. Data Service Layer (appwrite/config.js)

The Service class handles all post-related operations and file management, working alongside the authentication system.

Class Structure:


export class Service {

    client = new Client();

    Databases;

    bucket;

    constructor() {

        this.client

            .setEndpoint(conf.appwriteUrl)

            .setProject(conf.appwriteProjectId);

        this.Databases = new Databases(this.client);

        this.bucket = new Storage(this.client);

    }

}

Post Management Methods:

File Management Methods:

Authentication Flow

Application Initialization (App.jsx)

  1. App component mounts and triggers useEffect

  2. Session check via authService.getCurrentUser()

  3. State update based on session status:

- If user data exists → dispatch login({userData})

- If no user data → dispatch logout()

  1. Loading state managed until authentication check completes

// App.jsx

useEffect(() => {

  authService.getCurrentUser()

  .then((userData)=>{

    if(userData){

      dispatch(login({userData}))

    }else{

      dispatch(logout())

    }

  })

  .finally(() => {

    setLoading(false)

  })

}, [])

User Registration Flow (src/components/Signup.jsx)

  1. User submits registration form with name, email, and password

  2. Form validation occurs using react-hook-form

  3. authService.createAccount(data) called with validated data

  4. Appwrite creates account with account.create(ID.unique(), email, password, name)

  5. Automatic login - createAccount internally calls login() if account creation succeeds

  6. User data retrieval via authService.getCurrentUser()

  7. Redux state update via dispatch(login(userData))

  8. Navigation to home page ("/")


// Signup.jsx

const create = async(data) => {

  setError("")

  try {

    const userData = await authService.createAccount(data)

    if (userData) {

      const userData = await authService.getCurrentUser()

      if(userData) dispatch(login(userData));

      navigate("/")

    }

  } catch (error) {

    setError(error.message)

  }

}

User Login Flow (src/components/Login.jsx)

  1. User submits login form with email and password

  2. Form validation occurs using react-hook-form

  3. authService.login(data) creates session with Appwrite using createEmailPasswordSession()

  4. User data retrieval via authService.getCurrentUser()

  5. Redux state update via dispatch(authLogin(userData))

  6. Navigation to home page ("/")


// Login.jsx

const login = async(data) => {

  setError("")

  try {

    const session = await authService.login(data)

    if (session) {

      const userData = await authService.getCurrentUser()

      if(userData) dispatch(authLogin(userData));

      navigate("/")

    }

  } catch (error) {

    setError(error.message)

  }

}

User Logout Flow (src/components/Header/LogoutBtn.jsx)

  1. User clicks logout button

  2. authService.logout() terminates Appwrite sessions using account.deleteSessions()

  3. Redux state update via dispatch(logout())

  4. Automatic navigation handled by AuthLayout component based on updated auth state


// LogoutBtn.jsx

const logoutHandler = () => {

  authService.logout().then(() => {

    dispatch(logout())

  })

}

Route Protection System

AuthLayout Component (src/components/AuthLayout.jsx)

Acts as a Higher-Order Component (HOC) for route protection.

Parameters:

Protection Logic:


// AuthLayout.jsx

useEffect(() => {

  if(authentication && authStatus !== authentication){

    navigate("/login")  // Redirect to login if auth required but user not authenticated

  } else if(!authentication && authStatus !== authentication){

    navigate("/")       // Redirect to home if no auth required but user is authenticated

  }

  setLoader(false)

}, [authStatus, navigate, authentication])

Usage Pattern:

Complete Authentication State Access Points

Components Using Auth State:

  1. Header Component (src/components/Header/Header.jsx)

- Accesses: useSelector(state => state.auth.status)

- Purpose: Conditionally renders navigation items and logout button

- Logic:

- Shows Login/Signup buttons when !authStatus

- Shows All Posts/Add Post buttons when authStatus is true

- Displays LogoutBtn component when authenticated

- Navigation Items:

```javascript

const navItems = [

{ name: 'Home', slug: "/", active: true },

{ name: "Login", slug: "/login", active: !authStatus },

{ name: "Signup", slug: "/signup", active: !authStatus },

{ name: "All Posts", slug: "/all-posts", active: authStatus },

]

```

  1. AuthLayout Component (src/components/AuthLayout.jsx)

- Accesses: useSelector(state => state.auth.status)

- Purpose: Route protection and navigation control

- Loading State: Shows "Loading..." while checking auth status

- Conditional Rendering: Only renders children when loader is false

  1. Post Component (src/pages/Post.jsx)

- Accesses: useSelector(state => state.auth.userData)

- Purpose: Determines if current user is the author of a post

- Author Check: const isAuthor = post && userData ? post.userId === userData.$id : false

- Conditional Rendering: Shows Edit/Delete buttons only to post authors

- Actions Available to Authors:

- Edit button → navigates to /edit-post/${post.$id}

- Delete button → calls appwriteService.deletePost() and appwriteService.deleteFile()

  1. PostForm Component (src/components/post-form/PostForm.jsx)

- Accesses: useSelector(state => state.auth.userData)

- Purpose: Associates posts with the authenticated user during creation/editing

- User Data Usage:

- userId: userData.$id - Links post to user

- authorName: userData.name - Displays author name

- Security Check: Redirects to login if userData is not available

- Form Handling: Uses react-hook-form for form management

  1. LogoutBtn Component (src/components/Header/LogoutBtn.jsx)

- No Direct Auth Access: But dispatches logout action

- Functionality:

- Calls authService.logout()

- Dispatches logout() action to Redux store

Page-Level Authentication Integration

Protected Pages (Require Authentication):

  1. AddPost Page (src/pages/AddPost.jsx)

- Route: /add-post

- Protection: Wrapped with <AuthLayout authentication>

- Component: Renders PostForm component for creating new posts

  1. AllPost Page (src/pages/AllPost.jsx)

- Route: /all-posts

- Protection: Wrapped with <AuthLayout authentication>

- Functionality: Displays all active posts using appwriteService.getPosts([])

- Layout: Grid layout with PostCard components

  1. EditPost Page (src/pages/EditPost.jsx)

- Route: /edit-post/:slug

- Protection: Wrapped with <AuthLayout authentication>

- Functionality:

- Fetches existing post data via appwriteService.getPost(slug)

- Renders PostForm with pre-populated data

- Redirects to home if post not found

Public-Only Pages (Redirect if Authenticated):

  1. Login Page (src/pages/Login.jsx)

- Route: /login

- Protection: Wrapped with <AuthLayout authentication={false}>

- Component: Wrapper for Login component from components directory

  1. Signup Page (src/pages/Signup.jsx)

- Route: /signup

- Protection: Wrapped with <AuthLayout authentication={false}>

- Component: Wrapper for Signup component from components directory

Public Pages (No Authentication Required):

  1. Home Page (src/pages/Home.jsx)

- Route: /

- No Protection: Accessible to all users

- Dynamic Content: Shows posts if available, otherwise shows landing page

  1. Post Page (src/pages/Post.jsx)

- Route: /post/:slug

- No Protection: Anyone can view individual posts

- Author Features: Edit/Delete buttons only visible to post authors

Session Persistence and Redux State Management

How Redux Manages Auth State

  1. State Structure:

- status (boolean): Indicates if user is authenticated

- userData (object): Contains user information from Appwrite

  1. Action Creators:

- login(userData): Sets status to true and stores user data

- logout(): Sets status to false and clears user data

  1. State Access:

- Components access auth state via useSelector(state => state.auth.status)

- User data accessed via useSelector(state => state.auth.userData)

Session Persistence Flow

  1. Initial App Load:

- App component mounts and checks for existing session

- authService.getCurrentUser() queries Appwrite for active session

- If session exists, Redux store is updated with user data

- UI renders based on authentication status

  1. Session Storage:

- Appwrite SDK handles session storage internally (cookies/localStorage)

- No manual session token management needed in the application

- Sessions persist across browser refreshes

  1. Session Validation:

- AuthLayout component checks Redux auth state on route changes

- Protected routes redirect to login if no valid session

- Public-only routes redirect to home if session exists

  1. Session Termination:

- LogoutBtn component calls authService.logout()

- Appwrite SDK clears all sessions via deleteSessions()

- Redux store updated to reflect logged-out state

- UI updates based on new auth state

Error Handling

Authentication Errors:

Network/Service Errors:

Security Considerations

Current Implementation:

Potential Improvements:

File Dependencies

Core Auth Files:

Components Using Auth:

Authentication Data Flow Diagram


┌─────────────────┐     ┌───────────────┐     ┌────────────────┐

│                 │     │               │     │                │

│  User Actions   │────▶│  Auth Service │────▶│  Appwrite API  │

│                 │     │               │     │                │

└─────────────────┘     └───────────────┘     └────────────────┘

        │                      │                     │

        │                      │                     │

        │                      ▼                     │

        │              ┌───────────────┐             │

        │              │               │             │

        └─────────────▶│  Redux Store  │◀────────────┘

                       │               │

                       └───────────────┘

                              │

                              │

                              ▼

                     ┌─────────────────┐

                     │                 │

                     │  React UI       │

                     │  Components     │

                     │                 │

                     └─────────────────┘

Summary

This authentication system provides a complete user management solution with:

The system is well-structured, using modern React patterns and leveraging Appwrite's built-in authentication capabilities while maintaining a clean separation of concerns between authentication logic and UI components.