I want to be able to authenticate users with Oauth and protect the API endpoints with Basic authentication. How can I achieve this? My currently configuration doesn't work, unless I remove the Basic auth part and exclude api routes from authentication/authorization. In that case, Oauth authentication alone works perfectly.
auth.config.ts
import type { NextAuthConfig, Session } from 'next-auth';
import Google from "next-auth/providers/google"
import { SupabaseAdapter } from "@auth/supabase-adapter"
import { NextRequest } from 'next/server';
export const authConfig = {
pages: {
signIn: '/login'
},
callbacks: {
authorized({ auth, request: { nextUrl }} : {auth: null | Session, request: NextRequest}) {
const isLoggedIn = !!auth?.user;
const isOnHomePage = nextUrl.pathname === '/';
if (isOnHomePage) {
if (isLoggedIn) {
return true;
}
return false; // Redirect unauthenticated users to login page
} else if (isLoggedIn) {
return Response.redirect(new URL('/', nextUrl))
}
return true;
}
},
providers: [Google],
adapter: SupabaseAdapter({
url: process.env.SUPABASE_URL!,
secret: process.env.SUPABASE_SERVICE_ROLE_KEY!,
})
} satisfies NextAuthConfig;
auth.ts
import NextAuth from "next-auth";
import { authConfig } from './auth.config';
export const { handlers, signIn, signOut, auth } = NextAuth(authConfig)
middleware.ts
import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import { NextResponse } from 'next/server';
const { auth } = NextAuth(authConfig);
export default auth((req) => {
const url = new URL(req.url);
// Explicitly skip NextAuth API routes and session endpoint
if (url.pathname.startsWith('/api/auth/') || url.pathname === '/api/session') {
return NextResponse.next();
}
if (url.pathname.startsWith('/api/')) {
const authHeader = req.headers.get('authorization') ?? '';
if (!authHeader.startsWith('Basic ')) {
return new NextResponse('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="Restricted"' },
});
}
const base64 = authHeader.slice(6);
let decoded = '';
try {
if (typeof Buffer !== 'undefined' && typeof Buffer.from === 'function') {
decoded = Buffer.from(base64, 'base64').toString('utf-8');
} else if (typeof atob === 'function') {
decoded = atob(base64);
} else {
return new NextResponse('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="Restricted"' },
});
}
} catch (err) {
return new NextResponse('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="Restricted"' },
});
}
const [username, password] = decoded.split(':');
const validUsername = process.env.API_BASIC_AUTH_USER;
const validPassword = process.env.API_BASIC_AUTH_PASS;
if (!validUsername || !validPassword || username !== validUsername || password !== validPassword) {
return new NextResponse('Unauthorized', {
status: 401,
headers: { 'WWW-Authenticate': 'Basic realm="Restricted"' },
});
}
// Basic auth passed — continue to API handler
return NextResponse.next();
}
// Non-API routes: proceed (NextAuth will handle redirects/auth pages)
return NextResponse.next();
});
export const config = {
// apply middleware to API routes and app pages; do NOT use negated matcher entries
matcher: [
'/api/:path*',
'/((?!_next/static|_next/image|.*\\.png$).*)'
],
};
I get the followin error in the browser console:
providers.tsx:36 ClientFetchError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON. Read more at https://errors.authjs.dev#autherror
at fetchData (client.js:39:22)
at async getSession (react.js:97:21)
at async SessionProvider.useEffect [as _getSession] (react.js:253:43)