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:
Login/Signup : User provides credentials
OTP Verification : User enters 6-digit code from email
Session Creation : Token stored in secure cookie
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.
Standard API response object Whether login credentials are valid
Response message (e.g., “OTP sent to email”)
Client Component
Basic Usage
"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.
Registration data Organization information Team size (e.g., “1-10”, “10-50”, “50-100”)
Organization logo URL (can be empty string)
Standard API response object Whether registration succeeded
Client Component
With Logo Upload
"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.
OTP verification data 6-digit OTP code from email
Standard API response object Whether OTP verification succeeded
Response data containing token
Client Component
Complete Login Flow
"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.
Standard API response object Whether reset request succeeded
"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.
6-digit OTP code from email
Standard API response object Whether OTP verification succeeded
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.
Standard API response object Whether password reset succeeded
Client Component
Complete Reset Flow
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.
Client Component
With Confirmation
"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" ;