Skip to content

Instantly share code, notes, and snippets.

@Namaskar-1F64F
Created February 7, 2024 04:28
Show Gist options
  • Save Namaskar-1F64F/b185f1e25aa8d8e2c124053018c37084 to your computer and use it in GitHub Desktop.
Save Namaskar-1F64F/b185f1e25aa8d8e2c124053018c37084 to your computer and use it in GitHub Desktop.
NextAuth Next.js Middleware for Authenticating GitHub Apps.
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