Careeroza
Credentials and Google Providers
Google Login
- Create an OAuth client in Google Cloud Console (APIs and Services → Credentials).
- Authorized redirect URI:
http://localhost:3000/api/auth/callback/google(and your production domain). - Put the id and secret in
.env.local.
.env.local
.env.local
AUTH_SECRET=generated-by-npx-auth-secret
AUTH_GOOGLE_ID=1234567890-abc.apps.googleusercontent.com
AUTH_GOOGLE_SECRET=GOCSPX-xxxxxxxx// components/GoogleButton.tsx (server component with a form action)
import { signIn } from '@/auth';
export default function GoogleButton() {
return (
<form
action={async () => {
'use server';
await signIn('google', { redirectTo: '/account' });
}}
>
<button type="submit">Continue with Google</button>
</form>
);
}Email and Password
The Credentials provider lets you check an email and password against your own database. Store only a password hash (bcrypt or argon2).
// auth.ts
import NextAuth from 'next-auth';
import Credentials from 'next-auth/providers/credentials';
import Google from 'next-auth/providers/google';
import bcrypt from 'bcryptjs';
import { z } from 'zod';
import { prisma } from '@/lib/prisma';
const LoginSchema = z.object({ email: z.string().email(), password: z.string().min(8) });
export const { handlers, auth, signIn, signOut } = NextAuth({
session: { strategy: 'jwt' },
providers: [
Google,
Credentials({
credentials: { email: {}, password: {} },
authorize: async (raw) => {
const parsed = LoginSchema.safeParse(raw);
if (!parsed.success) return null;
const user = await prisma.user.findUnique({ where: { email: parsed.data.email } });
if (!user?.passwordHash) return null;
const ok = await bcrypt.compare(parsed.data.password, user.passwordHash);
return ok ? { id: user.id, name: user.name, email: user.email, role: user.role } : null;
},
}),
],
});Hashing Passwords
Example
Never store plain passwords. Node has scrypt built in; bcrypt and argon2 work the same way (salt + slow hash + constant-time compare). The salt is fixed here only so the output is stable:
import { scryptSync, timingSafeEqual } from 'node:crypto';
function hashPassword(password: string, salt: string): string {
return salt + ':' + scryptSync(password, salt, 32).toString('hex');
}
function verifyPassword(password: string, stored: string): boolean {
const [salt, hash] = stored.split(':');
const candidate = scryptSync(password, salt, 32);
return timingSafeEqual(candidate, Buffer.from(hash, 'hex'));
}
const stored = hashPassword('Shop@2026', 'a1b2c3d4e5f60718'); // real code: randomBytes(16).toString('hex')
console.log('stored:', stored.slice(0, 40) + '...');
console.log('correct password:', verifyPassword('Shop@2026', stored));
console.log('wrong password :', verifyPassword('shop@2026', stored));Output
stored: a1b2c3d4e5f60718:d29f6ec0b82c26322ad7fe7... correct password: true wrong password : false