feat(SW-162): Used token instead of cookie
This commit is contained in:
@@ -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",
|
||||||
|
|||||||
@@ -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
71
auth.ts
@@ -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
|
||||||
|
|||||||
@@ -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
2
types/jwt.d.ts
vendored
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user