Skip to content

Commit 2ddc9eb

Browse files
committed
feat: openid client v6 with more other improvements
1 parent 9c942b5 commit 2ddc9eb

37 files changed

+354
-275
lines changed

src/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"lucide-react": "^0.453.0",
5656
"next": "15.0.0",
5757
"next-themes": "^0.3.0",
58-
"openid-client": "^5.7.0",
58+
"openid-client": "^6.1.3",
5959
"prettier-plugin-organize-imports": "^4.1.0",
6060
"react": "^18.3.1",
6161
"react-day-picker": "^9.1.4",

src/pnpm-lock.yaml

Lines changed: 13 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/src/app/admin/layout.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AdminLayout from '@/layout/admin-layout'
2+
import React from "react";
23

34
export default function AdminRootLayout({ children }: { children: React.ReactNode }) {
45
return <AdminLayout>{children}</AdminLayout>

src/src/app/api/[...slug]/route.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@ import { NextRequest, NextResponse } from 'next/server'
33

44
const EXTERNAL_API_URL = process.env.NEXT_PUBLIC_API_URL
55

6-
export async function GET(request: NextRequest) {
6+
/**
7+
* Handles GET requests by forwarding them to an external API.
8+
* Retrieves the current session and uses the access token for authorization.
9+
*
10+
* @param {NextRequest} request - The incoming request object from Next.js.
11+
* @returns {Promise<Response>} - The response from the external API or an error response.
12+
*/
13+
export async function GET(request: NextRequest): Promise<Response> {
714
const session = await getSession()
15+
console.log('GET')
816
const path = request.nextUrl.pathname
917
console.log(`${EXTERNAL_API_URL}${path}${request.nextUrl.search}`)
1018
try {
@@ -20,6 +28,13 @@ export async function GET(request: NextRequest) {
2028
}
2129
}
2230

31+
/**
32+
* Handles POST requests by forwarding them to an external API.
33+
* Retrieves the current session and uses the access token for authorization.
34+
*
35+
* @param {NextRequest} request - The incoming request object from Next.js.
36+
* @returns {Promise<Response>} - The response from the external API.
37+
*/
2338
export async function POST(request: NextRequest) {
2439
const session = await getSession()
2540
const path = request.nextUrl.pathname
@@ -36,6 +51,13 @@ export async function POST(request: NextRequest) {
3651
} as any)
3752
}
3853

54+
/**
55+
* Handles PUT requests by forwarding them to an external API.
56+
* Retrieves the current session and uses the access token for authorization.
57+
*
58+
* @param {NextRequest} request - The incoming request object from Next.js.
59+
* @returns {Promise<Response>} - The response from the external API.
60+
*/
3961
export async function PUT(request: NextRequest) {
4062
const session = await getSession()
4163
const path = request.nextUrl.pathname
@@ -52,8 +74,14 @@ export async function PUT(request: NextRequest) {
5274
} as any)
5375
}
5476

77+
/**
78+
* Handles DELETE requests by forwarding them to an external API.
79+
* Retrieves the current session and uses the access token for authorization.
80+
*
81+
* @param {NextRequest} request - The incoming request object from Next.js.
82+
* @returns {Promise<Response>} - The response from the external API.
83+
*/
5584
export async function DELETE(request: NextRequest) {
56-
// reroute the request to the API
5785
const session = await getSession()
5886
const path = request.nextUrl.pathname
5987
const url = `${EXTERNAL_API_URL}${path}${request.nextUrl.search}`
@@ -65,4 +93,4 @@ export async function DELETE(request: NextRequest) {
6593
__tenant: session.tenantId === 'default' ? undefined : session.tenantId,
6694
} as any,
6795
})
68-
}
96+
}

src/src/app/auth/login/route.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
import { clientConfig } from '@/config'
22
import { getSession } from '@/lib/actions'
3-
import { getClient } from '@/lib/session-utils'
4-
import { generators } from 'openid-client'
5-
3+
import {getClientConfig} from '@/lib/session-utils'
4+
import * as client from 'openid-client'
65
export async function GET() {
76
const session = await getSession()
8-
session.code_verifier = generators.codeVerifier()
9-
const code_challenge = generators.codeChallenge(session.code_verifier)
10-
const client = await getClient()
11-
const url = client.authorizationUrl({
12-
scope: clientConfig.scope,
13-
audience: clientConfig.audience,
14-
redirect_uri: clientConfig.redirect_uri,
7+
let code_verifier = client.randomPKCECodeVerifier()
8+
let code_challenge = await client.calculatePKCECodeChallenge(code_verifier)
9+
const openIdClientConfig = await getClientConfig()
10+
let parameters: Record<string, string> = {
11+
"redirect_uri": clientConfig.redirect_uri,
12+
"scope": clientConfig.scope!,
1513
code_challenge,
16-
code_challenge_method: 'S256',
17-
__tenant: session.tenantId === 'default' ? undefined : session.tenantId,
18-
})
14+
"code_challenge_method": clientConfig.code_challenge_method,
15+
"__tenant": session.tenantId === 'default' ? "" : session.tenantId!,
16+
}
17+
let state!: string
18+
if (!openIdClientConfig.serverMetadata().supportsPKCE()) {
19+
state = client.randomState()
20+
parameters.state = state
21+
}
22+
let redirectTo = client.buildAuthorizationUrl(openIdClientConfig, parameters)
23+
session.code_verifier = code_verifier
24+
session.state = state
1925
await session.save()
20-
return Response.redirect(url)
26+
return Response.redirect(redirectTo.href)
2127
}

src/src/app/auth/logout/route.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
import { clientConfig } from '@/config'
22
import { getSession } from '@/lib/actions'
33
import { RedisSession, createRedisInstance } from '@/lib/redis'
4-
import { defaultSession, getClient } from '@/lib/session-utils'
5-
import { generators } from 'openid-client'
6-
4+
import {defaultSession, getClientConfig} from '@/lib/session-utils'
5+
import * as client from 'openid-client'
76
export async function GET() {
87
const session = await getSession()
98
const redis = createRedisInstance()
109
const redisKey = `session:${session.userInfo?.sub}`
11-
var redisSessionData = await redis.get(redisKey)
12-
var parsedSessionData = JSON.parse(redisSessionData!) as RedisSession
13-
const client = await getClient()
14-
var endSession = client.endSessionUrl({
10+
const redisSessionData = await redis.get(redisKey);
11+
const parsedSessionData = JSON.parse(redisSessionData!) as RedisSession;
12+
const openIdClientConfig = await getClientConfig()
13+
const endSessionUrl = client.buildEndSessionUrl(openIdClientConfig, {
1514
post_logout_redirect_uri: clientConfig.post_logout_redirect_uri,
1615
id_token_hint: parsedSessionData.access_token,
17-
state: generators.state(),
1816
})
1917
session.isLoggedIn = defaultSession.isLoggedIn
2018
session.access_token = defaultSession.access_token
2119
session.userInfo = defaultSession.userInfo
2220
await redis.del(session?.userInfo?.sub!)
2321
await session.save()
24-
return Response.redirect(endSession)
22+
return Response.redirect(endSessionUrl.href)
2523
}

src/src/app/auth/openiddict/route.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
import { clientConfig } from '@/config'
2+
import * as client from 'openid-client'
23
import { getSession } from '@/lib/actions'
34
import { RedisSession, createRedisInstance } from '@/lib/redis'
4-
import { getClient } from '@/lib/session-utils'
5+
import {getClientConfig} from '@/lib/session-utils'
56
import { NextRequest } from 'next/server'
67
export async function GET(request: NextRequest) {
78
const session = await getSession()
8-
const client = await getClient()
9-
const params = client.callbackParams(request as any)
10-
const tokenSet = await client.callback(clientConfig.redirect_uri, params, {
11-
code_verifier: session.code_verifier,
9+
const openIdClientConfig = await getClientConfig()
10+
const currentUrl = new URL(request.url)
11+
const tokenSet = await client.authorizationCodeGrant(openIdClientConfig, currentUrl, {
12+
pkceCodeVerifier: session.code_verifier,
13+
expectedState: session.state
1214
})
1315
const { access_token, refresh_token } = tokenSet
1416
session.isLoggedIn = true
1517
session.access_token = access_token
18+
let claims = tokenSet.claims()!
19+
const {sub} = claims
1620
// call userinfo endpoint to get user info
17-
const userinfo = await client.userinfo(tokenSet)
21+
const userinfo = await client.fetchUserInfo(openIdClientConfig, access_token, sub)
1822
// store userinfo in session
1923
session.userInfo = {
2024
sub: userinfo.sub,
Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,7 @@
1-
import { RedisSession, createRedisInstance } from '@/lib/redis'
2-
import { SessionData, getClient } from '@/lib/session-utils'
3-
import { sessionOptions } from '@/sessionOptions'
4-
import { getIronSession } from 'iron-session'
5-
import { cookies } from 'next/headers'
61
import { redirect } from 'next/navigation'
7-
import { NextRequest } from 'next/server'
8-
export async function GET(request: NextRequest) {
9-
let session = await getIronSession<SessionData>(await cookies(), sessionOptions)
10-
const redisKey = `session:${session?.userInfo?.sub!}`
11-
const redis = createRedisInstance()
12-
const client = await getClient()
13-
var redisSessionData = await redis.get(redisKey)
14-
var parsedSessionData = JSON.parse(redisSessionData!) as RedisSession
15-
var tokenSet = await client.refresh(parsedSessionData.refresh_token!)
16-
console.log('Token refreshed. New token:', tokenSet.access_token)
17-
session.access_token = tokenSet.access_token
18-
await session.save()
19-
var newRedisSessionData = {
20-
access_token: tokenSet.access_token,
21-
refresh_token: tokenSet.refresh_token,
22-
} as RedisSession
23-
await redis.set(redisKey, JSON.stringify(newRedisSessionData))
24-
await redis.quit()
2+
import {refreshToken} from "@/lib/auth";
3+
4+
export async function GET() {
5+
await refreshToken()
256
redirect('/')
267
}

src/src/app/auth/set-tenant/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import { redirect } from 'next/navigation'
55
OpenAPI.BASE = process.env.NEXT_PUBLIC_API_URL!
66
export async function GET() {
77
const session = await getSession()
8-
var host = await (await headers()).get('host')
8+
const host = (await headers()).get('host');
99
if (session.tenantId) {
1010
return
1111
}
12-
var tenantGuid = await tenantGetTenantGuid({ host: host! })
12+
const tenantGuid = await tenantGetTenantGuid({host: host!});
1313
session.tenantId = tenantGuid ?? 'default'
1414
await session.save()
1515
redirect('/')

src/src/app/layout.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
1-
import { OpenAPI } from '@/client'
21
import GoogleAnalytics from '@/components/analytics/google-analytics'
32
import UmamiAnalytics from '@/components/analytics/umami-analytics'
4-
import { getSession } from '@/lib/actions'
53
import ReactQueryProviders from '@/lib/provider/QueryClientProvider'
64
import { cn } from '@/lib/utils'
75
import type { Metadata } from 'next'
86
import { Inter as FontSans } from 'next/font/google'
97
import './globals.css'
8+
import React from "react";
9+
import {setUpLayoutConfig} from "@/lib/auth";
1010

11-
OpenAPI.BASE = process.env.NEXT_PUBLIC_API_URL!
12-
13-
OpenAPI.interceptors.request.use(async (options) => {
14-
const session = await getSession()
15-
options.headers = {
16-
...options.headers,
17-
Authorization: `Bearer ${session.access_token}`,
18-
__tenant: session.tenantId ?? '',
19-
}
20-
return options
21-
})
11+
setUpLayoutConfig()
2212

2313
const fontSans = FontSans({
2414
subsets: ['latin'],
@@ -41,6 +31,7 @@ export default function RootLayout({
4131
<meta charSet="utf-8" />
4232
<meta name="viewport" content="width=device-width, initial-scale=1" />
4333
<link rel="icon" href="/favicon.ico" />
34+
<title>Abp React</title>
4435
{process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL && process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID && (
4536
<UmamiAnalytics
4637
scriptUrl={process.env.NEXT_PUBLIC_UMAMI_SCRIPT_URL}

0 commit comments

Comments
 (0)