For information about authentication in a Next.js app, refer to this page: https://nextjs.org/learn/dashboard-app/adding-authentication this implementation of authentication is based on auth.js: https://authjs.dev/
Django SSO is a python package for Single Sign-On: https://github.com/davidhaker/django-sso
My project is a Next.js app with a Python Flask backend, and I use a Django SSO server for authentication.
config in Next.js app
auth.config.js
import type { NextAuthConfig } from "next-auth";
export const authConfig = {
pages: {
signIn: "/login",
},
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
return isLoggedIn;
},
async session({ session, token }) {
// @ts-ignore
session.user = { ...token.user };
return session;
},
async jwt({ token, user }) {
if (user) {
token.user = user;
}
return token;
},
},
providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
auth.ts
import NextAuth from "next-auth";
import Credentials from "next-auth/providers/credentials";
import { authConfig } from "./auth.config";
import { fetchUser } from "@/app/lib/actions";
export const { auth, signIn, signOut } = NextAuth({
...authConfig,
providers: [
Credentials({
async authorize(credentials) {
if (credentials.authenticated) {
const userInfo = await fetchUser(credentials.user_identy as string);
return {
...userInfo,
name: credentials.user_identy,
};
}
return null;
},
}),
],
});
middleware.ts
import NextAuth from "next-auth";
import { authConfig } from "./auth.config";
export default NextAuth(authConfig).auth;
export const config = {
// https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
matcher: ["/((?!api|_next/static|_next/image|.*\\.png$|sso).*)"],
};
Route in app.js
app/login/route.tsx
import { redirect } from "next/navigation";
import { fetchToken } from "@/app/lib/actions";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export async function GET(request: Request) {
const url = new URL(request.url);
const searchParams = url.searchParams;
const nextUrl = searchParams.get("callbackUrl") || "";
const ssoTokenResponse = await fetchToken(nextUrl);
const token = ssoTokenResponse.token;
if (token) {
cookies().set("auth_token", token);
redirect(`${process.env.NEXT_PUBLIC_SSO_AUTH_LOGIN}?sso=${token}`);
} else {
return NextResponse.json(
{ message: "Invalid credentials" },
{ status: 401 }
);
}
}
app/sso/accept/route.tsx
import { fetchAuthInfo, confirmAuth } from "@/app/lib/actions";
import { cookies } from "next/headers";
import { signIn } from "@/auth";
import { NextResponse } from "next/server";
export async function GET() {
const cookieStore = cookies();
const token = cookieStore.get("auth_token");
if (!token) {
return NextResponse.json(
{ message: "Invalid credentials" },
{ status: 401 }
);
}
const authInfo = await fetchAuthInfo(token.value);
await confirmAuth(token.value);
await signIn("credentials", authInfo);
return NextResponse.json({ message: "Login successful" }, { status: 200 });
}
app/sso/event/route.tsx
import { NextResponse } from "next/server";
import { postSsoEvent } from "@/app/lib/data";
export async function POST(request: Request) {
const body = await request.json();
await postSsoEvent(body);
return NextResponse.json({ message: "POST request" });
}
actions in next.js app
app/lib/actions.ts
"use server";
import { redirect } from "next/navigation";
import { VersionPostData } from "./definitions";
import { signOut } from "@/auth";
export async function fetchToken(nextUrl: string) {
const formData = new FormData();
formData.append("token", `${process.env.NEXT_PUBLIC_SSO_AUTH_SECRET}`);
formData.append("next_url", nextUrl);
const response = await fetch(`${process.env.NEXT_PUBLIC_SSO_AUTH_OBTAIN}`, {
method: "POST",
body: formData,
}).catch((error) => {
throw new Error(`认证服务连接失败, 请联系管理员: ${error}`);
});
return response.json();
}
export async function fetchAuthInfo(authToken: string) {
let formData = new FormData();
formData.append("token", `${process.env.NEXT_PUBLIC_SSO_AUTH_SECRET}`);
formData.append("authentication_token", authToken);
const response = await fetch(`${process.env.NEXT_PUBLIC_SSO_AUTH_GET}`, {
method: "POST",
body: formData,
});
return response.json();
}
export async function confirmAuth(authToken: string) {
let formData = new FormData();
formData.append("token", `${process.env.NEXT_PUBLIC_SSO_AUTH_SECRET}`);
formData.append("authentication_token", authToken);
const response = await fetch(`${process.env.NEXT_PUBLIC_SSO_AUTH_CONFIRM}`, {
method: "POST",
body: formData,
});
return response.json();
}
export async function logout(user_identy: string) {
let formData = new FormData();
formData.append("token", `${process.env.NEXT_PUBLIC_SSO_AUTH_SECRET}`);
formData.append("user_identy", user_identy);
const response = await fetch(`${process.env.NEXT_PUBLIC_SSO_LOGOUT}`, {
method: "POST",
body: formData,
});
await signOut();
return response.json();
}
export async function postSsoEvent(data: any) {
const response = await fetch(`${process.env.SSO_EVENT_URL}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
return response.json();
}
export async function fetchUser(username: string) {
const response = await fetch(`${process.env.VERMAN_API}/users/${username}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (response.status !== 200) {
return null;
}
const data = await response.json();
return data;
}
env config
.env
AUTH_SECRET="HrYDel8Bs8qA2QbYlxX96Nq1bqm/7/g1zbTD413HHgY="
AUTH_TRUST_HOST=true
NEXT_PUBLIC_SSO_AUTH_SECRET="Ri7rndq7LFD2MEi9xysU3Lu728FoJMOXHBe45UVR8za4YyxolD9XPklQogloaL91kjLjrG9H9wcp01voDvOKNWvIJ7XihMa8K03Chm5pv7tmwxflZHpeUPpooi6XG353"
NEXT_PUBLIC_SSO_AUTH_OBTAIN=http://localhost:8807/sso/obtain/
NEXT_PUBLIC_SSO_AUTH_LOGIN=http://localhost:8807/login/
NEXT_PUBLIC_SSO_AUTH_GET=http://localhost:8807/sso/get/
NEXT_PUBLIC_SSO_AUTH_CONFIRM=http://localhost:8807/sso/make_used/
NEXT_PUBLIC_SSO_LOGOUT=http://localhost:8807/sso/deauthenticate/