Created
February 7, 2024 04:28
-
-
Save Namaskar-1F64F/b185f1e25aa8d8e2c124053018c37084 to your computer and use it in GitHub Desktop.
NextAuth Next.js Middleware for Authenticating GitHub Apps.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { withAuth } from 'next-auth/middleware' | |
import { NextRequest, NextResponse } from 'next/server' | |
import { encode, getToken } from 'next-auth/jwt' | |
import { Octokit } from '@octokit/core' | |
import { GitHubAppUserAuthentication, createAppAuth } from '@octokit/auth-app' | |
import { logAndReportError } from './services/withErrorHandling' | |
import { enforceEnvVars } from 'shared-types/utils' | |
function signOut(request: NextRequest) { | |
const response = NextResponse.redirect( | |
new URL('/api/auth/signin', request.url) | |
) | |
request.cookies.getAll().forEach((cookie) => { | |
if (cookie.name.includes('next-auth')) response.cookies.delete(cookie.name) | |
}) | |
return response | |
} | |
function updateCookie( | |
sessionToken: string | null, | |
request: NextRequest, | |
response: NextResponse | |
) { | |
const sessionCookie = | |
process.env.VERCEL_ENV === 'development' | |
? 'next-auth.session-token' | |
: '__Secure-next-auth.session-token' | |
if (sessionToken) { | |
request.cookies.set(sessionCookie, sessionToken) | |
response = NextResponse.next({ | |
request: { | |
headers: request.headers, | |
}, | |
}) | |
// set response cookies to send back to browser | |
response.cookies.set(sessionCookie, sessionToken, { | |
httpOnly: true, | |
maxAge: 604800, | |
secure: process.env.NODE_ENV === 'production', | |
sameSite: 'lax', | |
}) | |
} else { | |
request.cookies.delete(sessionCookie) | |
response = NextResponse.next({ | |
request: { | |
headers: request.headers, | |
}, | |
}) | |
response.cookies.delete(sessionCookie) | |
} | |
return response | |
} | |
const { | |
PRIVATE_KEY, | |
GITHUB_APP_ID, | |
GITHUB_APP_CLIENT_ID, | |
GITHUB_APP_CLIENT_SECRET, | |
NEXTAUTH_SECRET, | |
} = enforceEnvVars([ | |
'PRIVATE_KEY', | |
'GITHUB_APP_ID', | |
'GITHUB_APP_CLIENT_ID', | |
'GITHUB_APP_CLIENT_SECRET', | |
'NEXTAUTH_SECRET', | |
]) | |
const upgradeOauthTokenWithCode = async ( | |
code: string | |
): Promise<string | undefined> => { | |
try { | |
const appOctokit = new Octokit({ | |
authStrategy: createAppAuth, | |
auth: { | |
appId: GITHUB_APP_ID, | |
privateKey: PRIVATE_KEY.replace(/\\n/g, '\n'), | |
clientId: GITHUB_APP_CLIENT_ID, | |
clientSecret: GITHUB_APP_CLIENT_SECRET, | |
}, | |
}) | |
const { token } = (await appOctokit.auth({ | |
type: 'oauth-user', | |
code, | |
})) as GitHubAppUserAuthentication | |
if (typeof token !== 'string') { | |
throw new Error('Authentication with code did not return a token.') | |
} | |
return token | |
} catch (error) { | |
logAndReportError(error) | |
} | |
} | |
/* | |
This upgrades the oauth session to an app token and adds it to the session cookie. | |
See https://github.com/nextauthjs/next-auth/discussions/9715 for more info. | |
*/ | |
const addAppTokenToCookie = async ( | |
req: NextRequest, | |
response: NextResponse | |
): Promise<{ | |
response: NextResponse | |
error?: string | |
}> => { | |
const jwt = await getToken({ req }) | |
if (!jwt) { | |
return { response: signOut(req), error: 'No jwt in session.' } | |
} | |
const url = new URL(req.url) | |
const code = url.searchParams.get('code') | |
const error = url.searchParams.get('error') | |
if (error) { | |
return { response, error } | |
} | |
if (!code) { | |
return { response, error: 'No code in query params.' } | |
} | |
const githubAppToken = await upgradeOauthTokenWithCode(code) | |
if (!githubAppToken) { | |
return { | |
response, | |
error: 'Failed to upgrade oauth token.', | |
} | |
} | |
try { | |
const newSession = await encode({ | |
secret: NEXTAUTH_SECRET, | |
token: { | |
...jwt, | |
githubAppAccessToken: githubAppToken, | |
}, | |
maxAge: 60 * 24 * 60 * 60, | |
}) | |
response = updateCookie(newSession, req, response) | |
} catch (error) { | |
logAndReportError(error) | |
response = updateCookie(null, req, response) | |
} | |
return { response } | |
} | |
export const config = { | |
// Use your secured routes: | |
matcher: ['/projects', '/new', '/new/import', '/auth/connection'], | |
} | |
export default withAuth(async function middleware(req) { | |
const url = new URL(req.url) | |
let response = NextResponse.next() | |
// use your secured route endpoint name | |
if (url.pathname === '/auth/connection') { | |
const { response: newResponse, error } = await addAppTokenToCookie( | |
req, | |
response | |
) | |
if (error) { | |
logAndReportError(error) | |
} | |
response = newResponse | |
} | |
return response | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment