NextJS Notes: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
|||
Line 32: | Line 32: | ||
"/course/:path*", | "/course/:path*", | ||
] | ] | ||
</syntaxhighlight> | |||
=Authentication= | |||
nextjs seems to use next-auth for this. There are 3 elements to configure | |||
==Session== | |||
Create a file called src/lib/next-auth.d.ts. This is an object to share between client and server. The example creates additional fields, it can be anything provide you can generate in the provider (see below) | |||
<syntaxhighlight lang="ts"> | |||
import 'next-auth' | |||
declare module 'next-auth' { | |||
interface Session extends DefaultSession { | |||
email: string | |||
name: string | |||
accessTokenExpires: number | |||
graphAccessToken?: string | |||
apiToken?: string | |||
} | |||
} | |||
</syntaxhighlight> | |||
==Provider== | |||
This differs depending on the provider but here is the Azure-ad example. | |||
<syntaxhighlight lang="ts"> | |||
import { AuthOptions } from 'next-auth' | |||
import { JWT } from 'next-auth/jwt' | |||
import AzureADProvider from 'next-auth/providers/azure-ad' | |||
interface AccessTokenResponse { | |||
access_token: string | |||
} | |||
export const getAccessTokenRequestOptions = (data: string): RequestInit => { | |||
return { | |||
method: 'POST', | |||
headers: { | |||
'Content-Type': 'application/x-www-form-urlencoded', | |||
}, | |||
body: data, | |||
} | |||
} | |||
const refreshAccessToken = async (oldAccessToken: JWT): Promise<string> => { | |||
const operation = 'Getting TEST API Refresh Access Token' | |||
let accessToken = '' | |||
try { | |||
const url = `${process.env.TEST_API_OAUTH_HOST}/${process.env.TEST_API_TENANT_ID}/oauth2/v2.0/token` | |||
const data = `client_id=${process.env.AZURE_AD_CLIENT_ID}&Client_secret=${process.env.AZURE_AD_CLIENT_SECRET}&grant_type=refresh_token&refresh_token=${oldAccessToken.refreshToken}` | |||
const requestOptions: RequestInit = getAccessTokenRequestOptions(data) | |||
const response = await fetch(url, requestOptions) | |||
if (!response.ok) | |||
throw new Error( | |||
`Error ${operation}. Error getting Access Token. Status returned: ${ | |||
response.statusText ? response.statusText : 'Unknown Status' | |||
}` | |||
) | |||
const jsonResponse = (await response.json()) as AccessTokenResponse | |||
if (!jsonResponse.access_token) | |||
throw new Error(`Error ${operation}. Error getting Access Token. Malformed response`) | |||
accessToken = jsonResponse.access_token | |||
} catch (err) { | |||
console.error('getAccessToken():error:', err) | |||
throw err | |||
} | |||
return accessToken | |||
} | |||
const getClientCredentialsToken = async (): Promise<string> => { | |||
const operation = 'Getting TEST API Access Token' | |||
let accessToken = '' | |||
try { | |||
const url = `${process.env.TEST_API_OAUTH_HOST}/${process.env.TEST_API_TENANT_ID}/oauth2/v2.0/token` | |||
const data = `client_id=${process.env.TEST_API_CLIENT_ID}&Client_secret=${process.env.TEST_API_CLIENT_SECRET}&grant_type=client_credentials&scope=${process.env.TEST_API_SCOPE}` | |||
const requestOptions: RequestInit = getAccessTokenRequestOptions(data) | |||
const response = await fetch(url, requestOptions) | |||
if (!response.ok) | |||
throw new Error( | |||
`Error ${operation}. Error getting Access Token. Status returned: ${ | |||
response.statusText ? response.statusText : 'Unknown Status' | |||
}` | |||
) | |||
const jsonResponse = (await response.json()) as AccessTokenResponse | |||
if (!jsonResponse.access_token) | |||
throw new Error(`Error ${operation}. Error getting Access Token. Malformed response`) | |||
accessToken = jsonResponse.access_token | |||
} catch (err) { | |||
console.error('getAccessToken():error:', err) | |||
throw err | |||
} | |||
return accessToken | |||
} | |||
export const authOptions: AuthOptions = { | |||
providers: [ | |||
AzureADProvider({ | |||
clientId: process.env.AZURE_AD_CLIENT_ID || '', | |||
clientSecret: process.env.AZURE_AD_CLIENT_SECRET || '', | |||
tenantId: process.env.AZURE_AD_TENANT_ID || '', | |||
authorization: { | |||
params: { | |||
scope: `offline_access openid profile email User.Read`, | |||
}, | |||
}, | |||
}), | |||
], | |||
session: { strategy: 'jwt' }, | |||
debug: false, | |||
callbacks: { | |||
async jwt({ token, account }) { | |||
if (account?.access_token && account.expires_at) { | |||
token.graphAccessToken = account.access_token | |||
token.idToken = account.id_token | |||
token.refreshToken = account.refresh_token | |||
token.accessTokenExpires = account.expires_at * 1000 | |||
} | |||
// Return previous token if the access token has not expired yet | |||
// @ts-ignore | |||
if (Date.now() < token.accessTokenExpires) { | |||
token.apiTokenDetails = await getClientCredentialsToken() | |||
return token | |||
} | |||
token.graphAccessToken = await refreshAccessToken(token) | |||
token.apiToken = await getClientCredentialsToken() | |||
return token | |||
// Access token has expired, refresh it | |||
// return token | |||
}, | |||
async session({ session, token }) { | |||
const newSession = { | |||
...session, | |||
name: token.name, | |||
email: token.email, | |||
accessTokenExpires: token.accessTokenExpires, | |||
graphAccessToken: token.graphAccessToken, | |||
apiToken: token.apiToken, | |||
} | |||
return newSession | |||
}, | |||
}, | |||
pages: { | |||
}, | |||
} | |||
</syntaxhighlight> | </syntaxhighlight> |
Revision as of 20:19, 17 October 2023
Introduction
This page is meant to capture parts of NextJS not covered by React
External Images
When using external images on a page you next to specify the allowed domains in nextjs.config.js
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "images.unsplash.com",
},
],
},
experimental: {
serverActions: true,
},
};
Middleware
Protecting routes can be achieved using a file called middleware.ts at the same level as project/src/
export {default} from "next-auth/middleware";
// NextAuth config
export const config = {
// Add protected routes here
matcher: [
"/",
"/admin/:path*",
"/course/:path*",
]
Authentication
nextjs seems to use next-auth for this. There are 3 elements to configure
Session
Create a file called src/lib/next-auth.d.ts. This is an object to share between client and server. The example creates additional fields, it can be anything provide you can generate in the provider (see below)
import 'next-auth'
declare module 'next-auth' {
interface Session extends DefaultSession {
email: string
name: string
accessTokenExpires: number
graphAccessToken?: string
apiToken?: string
}
}
Provider
This differs depending on the provider but here is the Azure-ad example.
import { AuthOptions } from 'next-auth'
import { JWT } from 'next-auth/jwt'
import AzureADProvider from 'next-auth/providers/azure-ad'
interface AccessTokenResponse {
access_token: string
}
export const getAccessTokenRequestOptions = (data: string): RequestInit => {
return {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data,
}
}
const refreshAccessToken = async (oldAccessToken: JWT): Promise<string> => {
const operation = 'Getting TEST API Refresh Access Token'
let accessToken = ''
try {
const url = `${process.env.TEST_API_OAUTH_HOST}/${process.env.TEST_API_TENANT_ID}/oauth2/v2.0/token`
const data = `client_id=${process.env.AZURE_AD_CLIENT_ID}&Client_secret=${process.env.AZURE_AD_CLIENT_SECRET}&grant_type=refresh_token&refresh_token=${oldAccessToken.refreshToken}`
const requestOptions: RequestInit = getAccessTokenRequestOptions(data)
const response = await fetch(url, requestOptions)
if (!response.ok)
throw new Error(
`Error ${operation}. Error getting Access Token. Status returned: ${
response.statusText ? response.statusText : 'Unknown Status'
}`
)
const jsonResponse = (await response.json()) as AccessTokenResponse
if (!jsonResponse.access_token)
throw new Error(`Error ${operation}. Error getting Access Token. Malformed response`)
accessToken = jsonResponse.access_token
} catch (err) {
console.error('getAccessToken():error:', err)
throw err
}
return accessToken
}
const getClientCredentialsToken = async (): Promise<string> => {
const operation = 'Getting TEST API Access Token'
let accessToken = ''
try {
const url = `${process.env.TEST_API_OAUTH_HOST}/${process.env.TEST_API_TENANT_ID}/oauth2/v2.0/token`
const data = `client_id=${process.env.TEST_API_CLIENT_ID}&Client_secret=${process.env.TEST_API_CLIENT_SECRET}&grant_type=client_credentials&scope=${process.env.TEST_API_SCOPE}`
const requestOptions: RequestInit = getAccessTokenRequestOptions(data)
const response = await fetch(url, requestOptions)
if (!response.ok)
throw new Error(
`Error ${operation}. Error getting Access Token. Status returned: ${
response.statusText ? response.statusText : 'Unknown Status'
}`
)
const jsonResponse = (await response.json()) as AccessTokenResponse
if (!jsonResponse.access_token)
throw new Error(`Error ${operation}. Error getting Access Token. Malformed response`)
accessToken = jsonResponse.access_token
} catch (err) {
console.error('getAccessToken():error:', err)
throw err
}
return accessToken
}
export const authOptions: AuthOptions = {
providers: [
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID || '',
clientSecret: process.env.AZURE_AD_CLIENT_SECRET || '',
tenantId: process.env.AZURE_AD_TENANT_ID || '',
authorization: {
params: {
scope: `offline_access openid profile email User.Read`,
},
},
}),
],
session: { strategy: 'jwt' },
debug: false,
callbacks: {
async jwt({ token, account }) {
if (account?.access_token && account.expires_at) {
token.graphAccessToken = account.access_token
token.idToken = account.id_token
token.refreshToken = account.refresh_token
token.accessTokenExpires = account.expires_at * 1000
}
// Return previous token if the access token has not expired yet
// @ts-ignore
if (Date.now() < token.accessTokenExpires) {
token.apiTokenDetails = await getClientCredentialsToken()
return token
}
token.graphAccessToken = await refreshAccessToken(token)
token.apiToken = await getClientCredentialsToken()
return token
// Access token has expired, refresh it
// return token
},
async session({ session, token }) {
const newSession = {
...session,
name: token.name,
email: token.email,
accessTokenExpires: token.accessTokenExpires,
graphAccessToken: token.graphAccessToken,
apiToken: token.apiToken,
}
return newSession
},
},
pages: {
},
}