@pedwise/next-firebase-auth-edge
Version:
Next.js 13 Firebase Authentication for Edge and server runtimes. Dedicated for Next 13 server components. Compatible with Next.js middleware.
152 lines (131 loc) • 4.07 kB
text/typescript
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { CookieSerializeOptions } from "cookie";
import { ServiceAccount } from "../auth/credential";
import {
appendAuthCookies,
removeAuthCookies,
setAuthCookies,
SetAuthCookiesOptions,
} from "./cookies";
import { getRequestCookiesTokens, GetTokensOptions } from "./tokens";
import {
getFirebaseAuth,
handleExpiredToken,
IdAndRefreshTokens,
Tokens,
} from "../auth";
export interface CreateAuthMiddlewareOptions {
loginPath: string;
logoutPath: string;
cookieName: string;
cookieSignatureKeys: string[];
cookieSerializeOptions: CookieSerializeOptions;
serviceAccount: ServiceAccount;
apiKey: string;
}
export async function createAuthMiddlewareResponse(
request: NextRequest,
options: CreateAuthMiddlewareOptions
): Promise<NextResponse> {
if (request.nextUrl.pathname === options.loginPath) {
return setAuthCookies(request.headers, {
cookieName: options.cookieName,
cookieSerializeOptions: options.cookieSerializeOptions,
cookieSignatureKeys: options.cookieSignatureKeys,
serviceAccount: options.serviceAccount,
apiKey: options.apiKey,
});
}
if (request.nextUrl.pathname === options.logoutPath) {
return removeAuthCookies(request.headers, {
cookieName: options.cookieName,
cookieSerializeOptions: options.cookieSerializeOptions,
});
}
return NextResponse.next();
}
export type HandleInvalidToken = () => Promise<NextResponse>;
export type HandleValidToken = (tokens: Tokens) => Promise<NextResponse>;
export type HandleError = (e: unknown) => Promise<NextResponse>;
export interface AuthenticationOptions
extends CreateAuthMiddlewareOptions,
GetTokensOptions {
checkRevoked?: boolean;
handleInvalidToken?: HandleInvalidToken;
handleValidToken?: HandleValidToken;
handleError?: HandleError;
}
export async function refreshAuthCookies(
idToken: string,
response: NextResponse,
options: SetAuthCookiesOptions
): Promise<IdAndRefreshTokens> {
const { getCustomIdAndRefreshTokens } = getFirebaseAuth(
options.serviceAccount,
options.apiKey
);
const idAndRefreshTokens = await getCustomIdAndRefreshTokens(
idToken,
options.apiKey
);
await appendAuthCookies(response, idAndRefreshTokens, options);
return idAndRefreshTokens;
}
const defaultInvalidTokenHandler = async () => NextResponse.next();
const defaultValidTokenHandler: HandleValidToken = async () =>
NextResponse.next();
export async function authentication(
request: NextRequest,
options: AuthenticationOptions
): Promise<NextResponse> {
const handleValidToken = options.handleValidToken ?? defaultValidTokenHandler;
const handleError = options.handleError ?? defaultInvalidTokenHandler;
const handleInvalidToken =
options.handleInvalidToken ?? defaultInvalidTokenHandler;
if (
[options.loginPath, options.logoutPath].includes(request.nextUrl.pathname)
) {
return createAuthMiddlewareResponse(request, options);
}
const { verifyIdToken, handleTokenRefresh } = getFirebaseAuth(
options.serviceAccount,
options.apiKey
);
const idAndRefreshTokens = await getRequestCookiesTokens(
request.cookies,
options
);
if (!idAndRefreshTokens) {
return handleInvalidToken();
}
return handleExpiredToken(
async () => {
const decodedToken = await verifyIdToken(
idAndRefreshTokens.idToken,
options.checkRevoked
);
return await handleValidToken({
token: idAndRefreshTokens.idToken,
decodedToken,
});
},
async () => {
const { token, decodedToken } = await handleTokenRefresh(
idAndRefreshTokens.refreshToken,
options.apiKey
);
return appendAuthCookies(
await handleValidToken({ token, decodedToken }),
{
idToken: token,
refreshToken: idAndRefreshTokens.refreshToken,
},
options
);
},
async (e) => {
return handleError(e);
}
);
}