Skip to main content

Overview

Authentication actions handle user login, registration, OTP verification, and password reset workflows. All functions are located in src/app/actions/auth.ts.
MicroCBM uses OTP-based authentication. After login or registration, users must verify a 6-digit code sent to their email before a session is created.

Authentication Flow

The typical authentication flow involves multiple steps:
  1. Login/Signup: User provides credentials
  2. OTP Verification: User enters 6-digit code from email
  3. Session Creation: Token stored in secure cookie
  4. Authenticated Requests: Token automatically included in all API calls

Login Operations

loginService

Initiates the login process. Returns success if credentials are valid; OTP will be sent to the user’s email.
payload
object
required
Login credentials
email
string
required
User email address
password
string
required
User password
response
ApiResponse
Standard API response object
success
boolean
Whether login credentials are valid
message
string
Response message (e.g., “OTP sent to email”)
statusCode
number
HTTP status code
"use client";
import { loginService } from "@/app/actions";
import { toast } from "sonner";
import { useState } from "react";

export function LoginForm() {
  const [email, setEmail] = useState("");
  const [showOtp, setShowOtp] = useState(false);

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await loginService({
      email: email,
      password: password,
    });
    
    if (response.success) {
      toast.success("OTP sent to your email");
      setShowOtp(true);
    } else {
      toast.error(response.message || "Login failed");
    }
  };

  return (
    <form onSubmit={handleLogin}>
      <input 
        type="email" 
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email" 
      />
      <input 
        type="password" 
        placeholder="Password" 
      />
      <button type="submit">Login</button>
      
      {showOtp && <OtpInput email={email} />}
    </form>
  );
}
This function does NOT create a session. You must call verifyOTPService with the OTP code to complete authentication.

Registration Operations

signupService

Registers a new user with their organization. Returns success if registration is valid; OTP will be sent to the user’s email.
formData
object
required
Registration data
user
object
required
User information
first_name
string
required
User’s first name
last_name
string
required
User’s last name
email
string
required
User’s email address
organization
object
required
Organization information
name
string
required
Organization name
industry
string
required
Industry sector
team_strength
string
required
Team size (e.g., “1-10”, “10-50”, “50-100”)
logo_url
string
required
Organization logo URL (can be empty string)
password
string
required
User password
response
ApiResponse
Standard API response object
success
boolean
Whether registration succeeded
message
string
Response message
"use client";
import { signupService } from "@/app/actions";
import { toast } from "sonner";
import { useState } from "react";

export function SignupForm() {
  const [email, setEmail] = useState("");
  const [showOtp, setShowOtp] = useState(false);

  const handleSignup = async (data: SignupFormData) => {
    const response = await signupService({
      user: {
        first_name: data.firstName,
        last_name: data.lastName,
        email: data.email,
      },
      organization: {
        name: data.organizationName,
        industry: data.industry,
        team_strength: data.teamSize,
        logo_url: "",
      },
      password: data.password,
    });
    
    if (response.success) {
      toast.success("Registration successful! Check your email for OTP");
      setEmail(data.email);
      setShowOtp(true);
    } else {
      toast.error(response.message || "Registration failed");
    }
  };

  return (
    <form onSubmit={handleSignup}>
      {/* Form fields */}
      {showOtp && <OtpInput email={email} />}
    </form>
  );
}
Like login, this function does NOT create a session. Call verifyOTPService to complete registration.

OTP Verification

verifyOTPService

Verifies the OTP code and creates an authenticated session. This must be called after loginService or signupService.
formData
object
required
OTP verification data
email
string
required
User’s email address
otp
string
required
6-digit OTP code from email
response
ApiResponse
Standard API response object
success
boolean
Whether OTP verification succeeded
message
string
Response message
data
object
Response data containing token
token
string
JWT authentication token
"use client";
import { verifyOTPService } from "@/app/actions";
import { toast } from "sonner";
import { useRouter } from "next/navigation";

export function OtpInput({ email }: { email: string }) {
  const router = useRouter();
  const [otp, setOtp] = useState("");

  const handleVerifyOtp = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await verifyOTPService({
      email,
      otp,
    });
    
    if (response.success) {
      toast.success("Login successful!");
      router.push("/dashboard");
    } else {
      toast.error(response.message || "Invalid OTP");
    }
  };

  return (
    <form onSubmit={handleVerifyOtp}>
      <input 
        type="text" 
        value={otp}
        onChange={(e) => setOtp(e.target.value)}
        placeholder="Enter 6-digit OTP" 
        maxLength={6}
      />
      <button type="submit">Verify</button>
    </form>
  );
}
This function automatically creates a session cookie with the JWT token. All subsequent requests will include authentication.

Password Reset Operations

requestPasswordResetService

Initiates password reset process by sending OTP to user’s email.
email
string
required
User’s email address
response
ApiResponse
Standard API response object
success
boolean
Whether reset request succeeded
message
string
Response message
"use client";
import { requestPasswordResetService } from "@/app/actions";
import { toast } from "sonner";

export function ForgotPasswordForm() {
  const [email, setEmail] = useState("");
  const [showOtpInput, setShowOtpInput] = useState(false);

  const handleRequestReset = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await requestPasswordResetService(email);
    
    if (response.success) {
      toast.success("Reset code sent to your email");
      setShowOtpInput(true);
    } else {
      toast.error(response.message || "Failed to send reset code");
    }
  };

  return (
    <form onSubmit={handleRequestReset}>
      <input 
        type="email" 
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Enter your email" 
      />
      <button type="submit">Send Reset Code</button>
      
      {showOtpInput && <PasswordResetOtpInput email={email} />}
    </form>
  );
}

verifyPasswordResetOTPService

Verifies the password reset OTP code.
email
string
required
User’s email address
otp
string
required
6-digit OTP code from email
response
ApiResponse
Standard API response object
success
boolean
Whether OTP verification succeeded
message
string
Response message
import { verifyPasswordResetOTPService } from "@/app/actions";
import { toast } from "sonner";

const handleVerifyOtp = async (email: string, otp: string) => {
  const response = await verifyPasswordResetOTPService(email, otp);
  
  if (response.success) {
    toast.success("OTP verified! Enter new password");
    setShowPasswordInput(true);
  } else {
    toast.error(response.message || "Invalid OTP");
  }
};

resetPasswordService

Resets the user’s password after OTP verification.
payload
object
required
Password reset data
email
string
required
User’s email address
password
string
required
New password
response
ApiResponse
Standard API response object
success
boolean
Whether password reset succeeded
message
string
Response message
import { resetPasswordService } from "@/app/actions";
import { toast } from "sonner";
import { useRouter } from "next/navigation";

export function ResetPasswordForm({ email }: { email: string }) {
  const router = useRouter();
  const [password, setPassword] = useState("");

  const handleResetPassword = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await resetPasswordService({
      email,
      password,
    });
    
    if (response.success) {
      toast.success("Password reset successful!");
      router.push("/auth/login");
    } else {
      toast.error(response.message || "Failed to reset password");
    }
  };

  return (
    <form onSubmit={handleResetPassword}>
      <input 
        type="password" 
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Enter new password" 
      />
      <button type="submit">Reset Password</button>
    </form>
  );
}

Session Management

logout

Logs out the current user by destroying the session cookie.
void
void
No return value
"use client";
import { logout } from "@/app/actions";
import { useRouter } from "next/navigation";
import { toast } from "sonner";

export function LogoutButton() {
  const router = useRouter();

  const handleLogout = async () => {
    await logout();
    toast.success("Logged out successfully");
    router.push("/auth/login");
  };

  return (
    <button onClick={handleLogout}>
      Logout
    </button>
  );
}
After logout, the user will be redirected to /auth/login by the middleware on any protected route access.

Complete Authentication Workflows

Login Workflow

import { loginService, verifyOTPService } from "@/app/actions";
import { toast } from "sonner";
import { useState } from "react";
import { useRouter } from "next/navigation";

export function LoginPage() {
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [otp, setOtp] = useState("");
  const [showOtp, setShowOtp] = useState(false);

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await loginService({ email, password });
    
    if (response.success) {
      toast.success("OTP sent to your email");
      setShowOtp(true);
    } else {
      toast.error(response.message || "Invalid credentials");
    }
  };

  const handleVerifyOtp = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await verifyOTPService({ email, otp });
    
    if (response.success) {
      toast.success("Login successful!");
      router.push("/dashboard");
    } else {
      toast.error(response.message || "Invalid OTP");
    }
  };

  return (
    <div>
      {!showOtp ? (
        <form onSubmit={handleLogin}>
          <input 
            type="email" 
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Email" 
          />
          <input 
            type="password" 
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password" 
          />
          <button type="submit">Login</button>
        </form>
      ) : (
        <form onSubmit={handleVerifyOtp}>
          <input 
            type="text" 
            value={otp}
            onChange={(e) => setOtp(e.target.value)}
            placeholder="Enter 6-digit OTP" 
            maxLength={6}
          />
          <button type="submit">Verify OTP</button>
        </form>
      )}
    </div>
  );
}

Password Reset Workflow

import { 
  requestPasswordResetService,
  verifyPasswordResetOTPService,
  resetPasswordService 
} from "@/app/actions";
import { toast } from "sonner";
import { useState } from "react";
import { useRouter } from "next/navigation";

export function ForgotPasswordPage() {
  const router = useRouter();
  const [email, setEmail] = useState("");
  const [otp, setOtp] = useState("");
  const [password, setPassword] = useState("");
  const [step, setStep] = useState(1); // 1: email, 2: otp, 3: password

  const handleRequestReset = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await requestPasswordResetService(email);
    
    if (response.success) {
      toast.success("Reset code sent to your email");
      setStep(2);
    } else {
      toast.error(response.message);
    }
  };

  const handleVerifyOtp = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await verifyPasswordResetOTPService(email, otp);
    
    if (response.success) {
      toast.success("OTP verified!");
      setStep(3);
    } else {
      toast.error(response.message);
    }
  };

  const handleResetPassword = async (e: React.FormEvent) => {
    e.preventDefault();
    
    const response = await resetPasswordService({ email, password });
    
    if (response.success) {
      toast.success("Password reset successful!");
      router.push("/auth/login");
    } else {
      toast.error(response.message);
    }
  };

  return (
    <div>
      {step === 1 && (
        <form onSubmit={handleRequestReset}>
          <input 
            type="email" 
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            placeholder="Enter your email" 
          />
          <button type="submit">Send Reset Code</button>
        </form>
      )}
      
      {step === 2 && (
        <form onSubmit={handleVerifyOtp}>
          <input 
            type="text" 
            value={otp}
            onChange={(e) => setOtp(e.target.value)}
            placeholder="Enter OTP" 
            maxLength={6}
          />
          <button type="submit">Verify OTP</button>
        </form>
      )}
      
      {step === 3 && (
        <form onSubmit={handleResetPassword}>
          <input 
            type="password" 
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Enter new password" 
          />
          <button type="submit">Reset Password</button>
        </form>
      )}
    </div>
  );
}

Security Considerations

Session Storage

Authentication tokens are stored in secure HTTP-only cookies:
  • Secure: Cookies are marked as secure in production
  • HTTP-Only: Not accessible via JavaScript
  • SameSite: CSRF protection enabled
  • Automatic: Token automatically included in all API requests

Token Management

Tokens are managed automatically by the requestWithAuth helper:
// src/app/actions/helpers.ts
import { cookies } from "next/headers";

export async function requestWithAuth(
  input: RequestInfo,
  init?: RequestInit
): Promise<Response> {
  const token = (await cookies()).get("token")?.value;
  const headers = new Headers(init?.headers || {});
  
  if (token) {
    headers.set("Authorization", `Bearer ${token}`);
  }
  
  return fetch(url, { ...init, headers });
}

Middleware Protection

All routes except /auth/* are protected by middleware:
// src/middleware.ts
export const config = {
  matcher: [
    /*
     * Match all request paths except:
     * - /auth/* (authentication pages)
     * - _next/static (static files)
     * - _next/image (image optimization)
     * - favicon.ico, etc.
     */
    "/((?!auth|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|manifest.webmanifest).*)",
  ],
};

Type Definitions

Import types from @/app/actions:
import { ApiResponse } from "@/app/actions";

Build docs developers (and LLMs) love