feat(SW-162): Used token instead of cookie

This commit is contained in:
Hrishikesh Vaipurkar
2024-08-09 17:45:29 +02:00
parent e7f7fb286e
commit 51df6bfd34
5 changed files with 44 additions and 70 deletions

View File

@@ -1,5 +1,5 @@
import { createActionURL } from "@auth/core" 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 { NextRequest, NextResponse } from "next/server"
import { AuthError } from "next-auth" 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_NEXTAUTH_URL: process.env.NEXTAUTH_URL })
console.log({ logout_env: process.env }) console.log({ logout_env: process.env })
const cookieStore = cookies()
cookieStore.set("_MFA-validated-cookie", "", { maxAge: 0 })
const headers = new Headers(nextHeaders()) const headers = new Headers(nextHeaders())
const signOutURL = createActionURL( const signOutURL = createActionURL(
"signout", "signout",

View File

@@ -86,16 +86,34 @@ export async function GET(
console.log({ login_env: process.env }) console.log({ login_env: process.env })
console.log({ login_redirectTo: redirectTo }) 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( const redirectUrl = await signIn(
signInProvider, "curity",
{ {
redirectTo, redirectTo,
redirect: false, redirect: false,
}, },
{ params
ui_locales: context.params.lang,
}
) )
if (redirectUrl) { if (redirectUrl) {

71
auth.ts
View File

@@ -1,5 +1,3 @@
import { encode } from "@auth/core/jwt"
import { cookies } from "next/headers"
import NextAuth from "next-auth" import NextAuth from "next-auth"
import { env } from "@/env/server" 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, clientId: env.CURITY_CLIENT_ID_USER,
clientSecret: env.CURITY_CLIENT_SECRET_USER, clientSecret: env.CURITY_CLIENT_SECRET_USER,
// FIXME: This is incorrect. We should not hard code this. // FIXME: This is incorrect. We should not hard code this.
@@ -46,49 +47,11 @@ const sharedConfig = {
login_with: profile.login_with, 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<User>
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<User> } satisfies OIDCConfig<User>
export const config = { export const config = {
debug: env.NEXTAUTH_DEBUG, debug: env.NEXTAUTH_DEBUG,
providers: [curityProvider, curityMFAProvider], providers: [curityProvider],
redirectProxyUrl: env.NEXTAUTH_REDIRECT_PROXY_URL, redirectProxyUrl: env.NEXTAUTH_REDIRECT_PROXY_URL,
trustHost: true, trustHost: true,
session: { session: {
@@ -140,23 +103,13 @@ export const config = {
async authorized({ auth, request }) { async authorized({ auth, request }) {
return true return true
}, },
async jwt({ account, session, token, trigger, user }) { async jwt({ account, token, trigger, user, profile }) {
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,
})
}
const loginType = getLoginType(user) 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 { return {
access_token: account.access_token, access_token: account.access_token,
expires_at: account.expires_at expires_at: account.expires_at
@@ -164,6 +117,8 @@ export const config = {
: undefined, : undefined,
refresh_token: account.refresh_token, refresh_token: account.refresh_token,
loginType, loginType,
mfa_scope: mfa_scope,
mfa_expires_at: mfa_expires_at,
} }
} else if (Date.now() < token.expires_at) { } else if (Date.now() < token.expires_at) {
return token return token

View File

@@ -1,4 +1,3 @@
import { cookies } from "next/headers"
import { NextResponse } from "next/server" import { NextResponse } from "next/server"
import { authRequired, mfaRequired } from "@/constants/routes/authRequired" import { authRequired, mfaRequired } from "@/constants/routes/authRequired"
@@ -55,13 +54,15 @@ export const middleware = auth(async (request) => {
nextUrlClone.hostname = publicUrl.hostname nextUrlClone.hostname = publicUrl.hostname
/** /**
* Function to validate MFA cookie expiry * Function to validate MFA from token data
* @returns boolean * @returns boolean
*/ */
function isMFAInvalid() { function isMFAInvalid() {
const isMFAPath = mfaRequired.includes(nextUrl.pathname) const isMFAPath = mfaRequired.includes(nextUrl.pathname)
const cookieStore = cookies() const isMFATokenValid = request.auth
return isMFAPath && !cookieStore.get("_MFA-validated-cookie")?.value ? request.auth.token.mfa_expires_at > Date.now()
: false
return isMFAPath && !(request.auth?.token.mfa_scope && isMFATokenValid)
} }
if (isLoggedIn && isMFAInvalid()) { if (isLoggedIn && isMFAInvalid()) {

2
types/jwt.d.ts vendored
View File

@@ -13,5 +13,7 @@ declare module "next-auth/jwt" {
expires_at: number expires_at: number
refresh_token: string refresh_token: string
loginType: LoginType loginType: LoginType
mfa_scope: boolean
mfa_expires_at: number
} }
} }