From 51df6bfd34746a94ad0672fb3a37c81dc7276aed Mon Sep 17 00:00:00 2001 From: Hrishikesh Vaipurkar Date: Fri, 9 Aug 2024 17:45:29 +0200 Subject: [PATCH] feat(SW-162): Used token instead of cookie --- app/[lang]/(live)/(protected)/logout/route.ts | 4 +- app/[lang]/(live)/(public)/login/route.ts | 28 ++++++-- auth.ts | 71 ++++--------------- middlewares/authRequired.ts | 9 +-- types/jwt.d.ts | 2 + 5 files changed, 44 insertions(+), 70 deletions(-) diff --git a/app/[lang]/(live)/(protected)/logout/route.ts b/app/[lang]/(live)/(protected)/logout/route.ts index 7afa19089..38b98361a 100644 --- a/app/[lang]/(live)/(protected)/logout/route.ts +++ b/app/[lang]/(live)/(protected)/logout/route.ts @@ -1,5 +1,5 @@ import { createActionURL } from "@auth/core" -import { cookies, headers as nextHeaders } from "next/headers" +import { headers as nextHeaders } from "next/headers" import { NextRequest, NextResponse } from "next/server" import { AuthError } from "next-auth" @@ -63,8 +63,6 @@ export async function GET( console.log({ logout_NEXTAUTH_URL: process.env.NEXTAUTH_URL }) console.log({ logout_env: process.env }) - const cookieStore = cookies() - cookieStore.set("_MFA-validated-cookie", "", { maxAge: 0 }) const headers = new Headers(nextHeaders()) const signOutURL = createActionURL( "signout", diff --git a/app/[lang]/(live)/(public)/login/route.ts b/app/[lang]/(live)/(public)/login/route.ts index 69884a944..5ccb67d44 100644 --- a/app/[lang]/(live)/(public)/login/route.ts +++ b/app/[lang]/(live)/(public)/login/route.ts @@ -86,16 +86,34 @@ export async function GET( console.log({ login_env: process.env }) console.log({ login_redirectTo: redirectTo }) - const signInProvider = isMFA ? "curity-mfa" : "curity" + const params = isMFA + ? { + ui_locales: context.params.lang, + scope: ["profile_update", "openid", "profile"].join(" "), + /** + * The below acr value is required as for New Web same Curity Client is used for MFA + * while in current web it is being setup using different Curity Client + */ + acr_values: + "urn:se:curity:authentication:otp-authenticator:OTP-Authenticator_web", + } + : { + ui_locales: context.params.lang, + scope: ["openid", "profile"].join(" "), + /** + * The `acr_values` param is used to make Curity display the proper login + * page for Scandic. Without the parameter Curity presents some choices + * to the user which we do not want. + */ + acr_values: "acr", + } const redirectUrl = await signIn( - signInProvider, + "curity", { redirectTo, redirect: false, }, - { - ui_locales: context.params.lang, - } + params ) if (redirectUrl) { diff --git a/auth.ts b/auth.ts index 5cb359970..4eb9aaca0 100644 --- a/auth.ts +++ b/auth.ts @@ -1,5 +1,3 @@ -import { encode } from "@auth/core/jwt" -import { cookies } from "next/headers" import NextAuth from "next-auth" import { env } from "@/env/server" @@ -22,7 +20,10 @@ function getLoginType(user: User) { } } -const sharedConfig = { +const curityProvider = { + id: "curity", + name: "Curity", + type: "oidc", clientId: env.CURITY_CLIENT_ID_USER, clientSecret: env.CURITY_CLIENT_SECRET_USER, // FIXME: This is incorrect. We should not hard code this. @@ -46,49 +47,11 @@ const sharedConfig = { login_with: profile.login_with, } }, -} - -const curityProvider = { - ...sharedConfig, - id: "curity", - name: "Curity", - type: "oidc", - authorization: { - ...sharedConfig.authorization, - params: { - scope: ["openid", "profile"].join(" "), - /** - * The `acr_values` param is used to make Curity display the proper login - * page for Scandic. Without the parameter Curity presents some choices - * to the user which we do not want. - */ - acr_values: "acr", - }, - }, -} satisfies OIDCConfig - -const curityMFAProvider = { - ...sharedConfig, - id: "curity-mfa", - name: "Curity MFA", - type: "oidc", - authorization: { - ...sharedConfig.authorization, - params: { - scope: ["profile_update", "openid", "profile"].join(" "), - /** - * The below acr value is required as for New Web same Curity Client is used for MFA - * while in current web it is being setup using different Curity Client ID and secret - */ - acr_values: - "urn:se:curity:authentication:otp-authenticator:OTP-Authenticator_web", - }, - }, } satisfies OIDCConfig export const config = { debug: env.NEXTAUTH_DEBUG, - providers: [curityProvider, curityMFAProvider], + providers: [curityProvider], redirectProxyUrl: env.NEXTAUTH_REDIRECT_PROXY_URL, trustHost: true, session: { @@ -140,23 +103,13 @@ export const config = { async authorized({ auth, request }) { return true }, - async jwt({ account, session, token, trigger, user }) { - if (account?.provider == "curity-mfa") { - const cookieStore = cookies() - // As new scope/token is added to existing session we will add separate cookie to validate MFA done - cookieStore.set({ - name: "_MFA-validated-cookie", - value: "true", - httpOnly: true, - sameSite: "lax", - expires: token.expires_at - ? token.expires_at * 1000 - : Date.now() + 10 * 60 * 1000, - }) - } - + async jwt({ account, token, trigger, user, profile }) { const loginType = getLoginType(user) - if (account) { + if (trigger === "signIn" && account) { + const mfa_scope = + profile?.amr == + "urn:se:curity:authentication:otp-authenticator:OTP-Authenticator_web" + const mfa_expires_at = mfa_scope ? Date.now() + 10 * 60 * 1000 : 0 return { access_token: account.access_token, expires_at: account.expires_at @@ -164,6 +117,8 @@ export const config = { : undefined, refresh_token: account.refresh_token, loginType, + mfa_scope: mfa_scope, + mfa_expires_at: mfa_expires_at, } } else if (Date.now() < token.expires_at) { return token diff --git a/middlewares/authRequired.ts b/middlewares/authRequired.ts index aa44e910a..67581369d 100644 --- a/middlewares/authRequired.ts +++ b/middlewares/authRequired.ts @@ -1,4 +1,3 @@ -import { cookies } from "next/headers" import { NextResponse } from "next/server" import { authRequired, mfaRequired } from "@/constants/routes/authRequired" @@ -55,13 +54,15 @@ export const middleware = auth(async (request) => { nextUrlClone.hostname = publicUrl.hostname /** - * Function to validate MFA cookie expiry + * Function to validate MFA from token data * @returns boolean */ function isMFAInvalid() { const isMFAPath = mfaRequired.includes(nextUrl.pathname) - const cookieStore = cookies() - return isMFAPath && !cookieStore.get("_MFA-validated-cookie")?.value + const isMFATokenValid = request.auth + ? request.auth.token.mfa_expires_at > Date.now() + : false + return isMFAPath && !(request.auth?.token.mfa_scope && isMFATokenValid) } if (isLoggedIn && isMFAInvalid()) { diff --git a/types/jwt.d.ts b/types/jwt.d.ts index ebbc2e2f8..a089714e5 100644 --- a/types/jwt.d.ts +++ b/types/jwt.d.ts @@ -13,5 +13,7 @@ declare module "next-auth/jwt" { expires_at: number refresh_token: string loginType: LoginType + mfa_scope: boolean + mfa_expires_at: number } }