Created
September 6, 2024 02:13
-
-
Save bugproof/dbef4972bc3d8c57b638490e316f42fa to your computer and use it in GitHub Desktop.
Kinde Auth + Paraglide-Next
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import {NextRequest, NextResponse} from 'next/server'; | |
| import jwt_decode, {JwtPayload} from 'jwt-decode'; | |
| import {isTokenValid} from '@kinde-oss/kinde-auth-nextjs'; | |
| import {removeTrailingSlash} from "next/dist/shared/lib/router/utils/remove-trailing-slash"; | |
| const KINDE_SITE_URL = removeTrailingSlash(process.env.KINDE_SITE_URL as string); | |
| interface Options { | |
| isReturnToCurrentPage?: boolean; | |
| loginPage?: string; | |
| publicPaths?: string[]; | |
| redirectURLBase?: string; | |
| isAuthorized?: (params: { req: NextRequest; token: any }) => boolean; | |
| lang?: string; | |
| } | |
| interface User { | |
| family_name: string; | |
| given_name: string; | |
| email: string; | |
| id: string; | |
| picture: string; | |
| } | |
| interface OnSuccessParams { | |
| token: any; | |
| user: User; | |
| } | |
| type OnSuccessFunction = (params: OnSuccessParams) => Promise<NextResponse | void>; | |
| const handleMiddleware = async ( | |
| req: NextRequest, | |
| options?: Options, | |
| onSuccess?: OnSuccessFunction | |
| ): Promise<NextResponse | void> => { | |
| const { pathname } = req.nextUrl; | |
| const isReturnToCurrentPage = options?.isReturnToCurrentPage; | |
| const loginPage = options?.loginPage || '/api/auth/login'; | |
| let publicPaths = ['/_next', '/favicon.ico']; | |
| if (options?.publicPaths !== undefined) { | |
| if (Array.isArray(options?.publicPaths)) { | |
| publicPaths = options.publicPaths; | |
| } | |
| } | |
| let loginRedirectUrl = isReturnToCurrentPage | |
| ? `${loginPage}?post_login_redirect_url=${pathname}` | |
| : loginPage; | |
| if (options?.lang) { | |
| loginRedirectUrl += `${loginRedirectUrl.includes('?') ? '&' : '?'}lang=${options.lang}`; | |
| } | |
| if (loginPage === pathname || publicPaths.some((p) => pathname.startsWith(p))) | |
| return; | |
| const kindeToken = req.cookies.get('access_token'); | |
| if (!kindeToken) { | |
| return NextResponse.redirect( | |
| new URL(loginRedirectUrl, options?.redirectURLBase || KINDE_SITE_URL) | |
| ); | |
| } | |
| const accessTokenValue = jwt_decode<JwtPayload>(req.cookies.get('access_token')!.value); | |
| const idTokenValue = jwt_decode<JwtPayload & User>(req.cookies.get('id_token')?.value ?? ''); | |
| const isAuthorized = options?.isAuthorized | |
| ? options.isAuthorized({ req, token: accessTokenValue }) | |
| : isTokenValid(kindeToken.value); | |
| if (isAuthorized && onSuccess) { | |
| return await onSuccess({ | |
| token: accessTokenValue, | |
| user: { | |
| family_name: idTokenValue.family_name, | |
| given_name: idTokenValue.given_name, | |
| email: idTokenValue.email, | |
| id: idTokenValue.sub as string, | |
| picture: idTokenValue.picture | |
| } | |
| }); | |
| } | |
| if (isAuthorized) { | |
| return; | |
| } | |
| return NextResponse.redirect( | |
| new URL(loginRedirectUrl, options?.redirectURLBase || KINDE_SITE_URL) | |
| ); | |
| }; | |
| interface KindeAuth { | |
| user: User; | |
| token: string; | |
| } | |
| type MiddlewareFunction = ( | |
| req: NextRequest & { kindeAuth?: KindeAuth }, | |
| ...args: any[] | |
| ) => Promise<NextResponse | void>; | |
| export function withCustomAuth( | |
| reqOrOptionsOrMiddleware?: NextRequest | Options | MiddlewareFunction, | |
| options?: Options | |
| ): MiddlewareFunction | Promise<NextResponse | void> { | |
| // Case 1: Called with NextRequest (and possibly options) | |
| if (reqOrOptionsOrMiddleware instanceof NextRequest) { | |
| return handleMiddleware(reqOrOptionsOrMiddleware, options); | |
| } | |
| // Case 2: Called with options or middleware function | |
| if (typeof reqOrOptionsOrMiddleware === 'function') { | |
| // Case 2a: Called with middleware function | |
| return async (req: NextRequest & { kindeAuth?: KindeAuth }, ...args: any[]) => | |
| await handleMiddleware(req, options, async ({ token, user }) => { | |
| req.kindeAuth = { token, user }; | |
| return await reqOrOptionsOrMiddleware(req, ...args); | |
| }); | |
| } | |
| // Case 2b: Called with options or no arguments | |
| const middlewareOptions = reqOrOptionsOrMiddleware as Options | undefined; | |
| return async (req: NextRequest) => await handleMiddleware(req, middlewareOptions); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import {NextRequest, NextResponse} from 'next/server'; | |
| import { middleware as i18nMiddleware } from "@/lib/i18n"; | |
| import {withCustomAuth} from "@/customAuthMiddleware"; | |
| const protectedPaths = ["/dashboard", "/checkout"]; | |
| export default async function middleware(request: NextRequest) { | |
| // Detect language | |
| const lang = i18nMiddleware.detectLanguage(request); | |
| // Function to check if the path is protected | |
| const isProtectedPath = (path: string) => { | |
| return protectedPaths.some(protectedPath => path.startsWith(protectedPath)); | |
| }; | |
| // Get path without language prefix (if it exists) | |
| const pathWithoutLang = request.nextUrl.pathname.replace(/^\/[a-z]{2}\//, '/'); | |
| if (isProtectedPath(pathWithoutLang)) { | |
| // For protected routes, apply Kinde Auth | |
| const kindeLang = lang === "pt" ? "pt-PT" : lang; | |
| const authResult = await withCustomAuth(request, { | |
| isReturnToCurrentPage: true, | |
| lang: kindeLang, | |
| loginPage: pathWithoutLang === '/checkout' ? '/api/auth/register' : '/api/auth/login' | |
| // Add any other options you need for withAuth here | |
| }); | |
| if (authResult) { | |
| // If authResult is not undefined, it means we need to redirect (e.g., to login) | |
| return authResult; | |
| } | |
| } | |
| // If we reach here, either the route is not protected or the user is authenticated | |
| // Return the i18n response (or the default response if i18n didn't modify anything) | |
| return i18nMiddleware.getResponse(request, lang) || NextResponse.next(); | |
| } | |
| export const config = { | |
| matcher: [ | |
| /* | |
| * Match all request paths except for the ones starting with: | |
| * - api (API routes) | |
| * - _next/static (static files) | |
| * - _next/image (image optimization files) | |
| * - favicon.ico, sitemap.xml, robots.txt (metadata files) | |
| */ | |
| '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)' | |
| ] | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment