Back to Documentation
Tutorial45 minutes
Implementing Authentication
Secure your application with Munashe Tech Security and implement comprehensive user authentication
Prerequisites
- Existing Next.js 14+ application with App Router
- Munashe Tech Security account and API credentials
- Understanding of React hooks and server/client components
- Database configured (PostgreSQL, MySQL, or MongoDB)
Step-by-Step Guide
1
Install Munashe Tech Security SDK
3 min
- Add the Security SDK to your project
- Configure environment variables
- Initialize the authentication module
Code
# Install the SDK
npm install @munashetech/security
# Add to .env.local
NEXT_PUBLIC_MT_APP_ID=your_app_id
MT_SECRET_KEY=your_secret_key
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=generate_random_secret_here2
Configure Authentication Provider
8 min
- Set up NextAuth with Munashe Tech provider
- Configure session strategy
- Define authentication callbacks
Code
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import { MunasheTechProvider } from '@munashetech/security/nextauth';
const handler = NextAuth({
providers: [
MunasheTechProvider({
clientId: process.env.NEXT_PUBLIC_MT_APP_ID!,
clientSecret: process.env.MT_SECRET_KEY!,
// Enable multi-factor authentication
mfaEnabled: true,
// Choose authentication methods
authMethods: ['email', 'oauth', 'magic-link']
})
],
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
callbacks: {
async jwt({ token, user, account }) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.id = token.id;
session.user.role = token.role;
}
return session;
}
},
pages: {
signIn: '/auth/signin',
signOut: '/auth/signout',
error: '/auth/error',
}
});
export { handler as GET, handler as POST };3
Create Sign-In Page
10 min
- Build a custom sign-in interface
- Add social login buttons
- Implement error handling
Code
// app/auth/signin/page.tsx
'use client';
import { signIn } from 'next-auth/react';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
export default function SignIn() {
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const handleEmailSignIn = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const result = await signIn('email', {
email,
callbackUrl: '/dashboard',
redirect: false
});
if (result?.error) {
console.error('Sign in error:', result.error);
}
} catch (error) {
console.error('Sign in failed:', error);
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-muted/30">
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle className="text-2xl text-center">Welcome Back</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<form onSubmit={handleEmailSignIn} className="space-y-4">
<div>
<Input
type="email"
placeholder="Enter your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? 'Signing in...' : 'Sign in with Email'}
</Button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="space-y-2">
<Button
variant="outline"
className="w-full"
onClick={() => signIn('google', { callbackUrl: '/dashboard' })}
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
{/* Google icon SVG */}
</svg>
Google
</Button>
<Button
variant="outline"
className="w-full"
onClick={() => signIn('github', { callbackUrl: '/dashboard' })}
>
<svg className="w-5 h-5 mr-2" viewBox="0 0 24 24">
{/* GitHub icon SVG */}
</svg>
GitHub
</Button>
</div>
</CardContent>
</Card>
</div>
);
}4
Protect Routes with Middleware
7 min
- Create authentication middleware
- Define protected route patterns
- Handle unauthorized access
Code
// middleware.ts
import { withAuth } from 'next-auth/middleware';
import { NextResponse } from 'next/server';
export default withAuth(
function middleware(req) {
const token = req.nextauth.token;
const isAuth = !!token;
const isAuthPage = req.nextUrl.pathname.startsWith('/auth');
if (isAuthPage) {
if (isAuth) {
return NextResponse.redirect(new URL('/dashboard', req.url));
}
return null;
}
if (!isAuth) {
let from = req.nextUrl.pathname;
if (req.nextUrl.search) {
from += req.nextUrl.search;
}
return NextResponse.redirect(
new URL(`/auth/signin?from=${encodeURIComponent(from)}`, req.url)
);
}
},
{
callbacks: {
authorized: ({ token }) => !!token,
},
}
);
export const config = {
matcher: [
'/dashboard/:path*',
'/profile/:path*',
'/settings/:path*',
'/api/protected/:path*'
],
};5
Add Session Management
8 min
- Create session provider wrapper
- Access user session in components
- Implement sign-out functionality
Code
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }: { children: React.ReactNode }) {
return <SessionProvider>{children}</SessionProvider>;
}
// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>
{children}
</Providers>
</body>
</html>
);
}
// components/user-nav.tsx
'use client';
import { useSession, signOut } from 'next-auth/react';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
export function UserNav() {
const { data: session } = useSession();
if (!session?.user) return null;
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
<Avatar className="h-8 w-8">
<AvatarImage src={session.user.image} alt={session.user.name} />
<AvatarFallback>{session.user.name?.[0]}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">
{session.user.name}
</p>
<p className="text-xs leading-none text-muted-foreground">
{session.user.email}
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => signOut()}>
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}6
Implement Role-Based Access Control
9 min
- Define user roles and permissions
- Create authorization helpers
- Protect components based on roles
Code
// lib/auth/permissions.ts
export enum Role {
USER = 'user',
ADMIN = 'admin',
MODERATOR = 'moderator'
}
export const permissions = {
[Role.USER]: ['read:own', 'write:own'],
[Role.MODERATOR]: ['read:own', 'write:own', 'read:all', 'moderate:content'],
[Role.ADMIN]: ['read:all', 'write:all', 'delete:all', 'manage:users']
};
export function hasPermission(role: Role, permission: string): boolean {
return permissions[role]?.includes(permission) ?? false;
}
// hooks/usePermissions.ts
import { useSession } from 'next-auth/react';
import { hasPermission, Role } from '@/lib/auth/permissions';
export function usePermissions() {
const { data: session } = useSession();
const userRole = (session?.user?.role as Role) ?? Role.USER;
return {
can: (permission: string) => hasPermission(userRole, permission),
isAdmin: userRole === Role.ADMIN,
isModerator: userRole === Role.MODERATOR,
role: userRole
};
}
// components/admin-only.tsx
'use client';
import { usePermissions } from '@/hooks/usePermissions';
export function AdminOnly({ children }: { children: React.ReactNode }) {
const { isAdmin } = usePermissions();
if (!isAdmin) return null;
return <>{children}</>;
}
// Usage in components
import { AdminOnly } from '@/components/admin-only';
export function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<AdminOnly>
<Button variant="destructive">Delete All Data</Button>
</AdminOnly>
</div>
);
}Security Best Practices
- Never expose secrets: Keep API keys and secrets in environment variables, never commit them to version control
- Use HTTPS only: Always use HTTPS in production to encrypt data in transit
- Implement rate limiting: Protect your authentication endpoints from brute force attacks
- Enable MFA: Offer multi-factor authentication for enhanced security
- Regular security audits: Periodically review and update your authentication implementation
Testing Authentication
Verify your authentication implementation with these tests:
- 1.Test sign-in with valid credentials
- 2.Verify protected routes redirect to sign-in when not authenticated
- 3.Test sign-out functionality clears session
- 4.Verify role-based access control works correctly
- 5.Test social login providers (Google, GitHub)
- 6.Verify session persistence across page reloads
Common Issues
- • Redirect loops: Check middleware configuration and callback URLs
- • Session not persisting: Verify NEXTAUTH_SECRET is set correctly
- • OAuth errors: Confirm callback URLs match in provider settings
- • Database errors: Ensure database adapter is configured properly
Next Steps
Add Two-Factor Auth
Enhance security with TOTP-based 2FA
Implement API Authentication
Secure your API routes with JWT tokens
Need Help with Security?
Our security experts are here to help protect your application