I am currently using the credentials provider to verify a user on an LDAP. I am also using Hasura as a backend which requires certain claims in the jwt. So a custom encode and decode function is required. This is where i run into my issues. If i used the default encoding for Next-Auth everything works and I am able to log into my app. When i create my own encode and decode function i cannot get past the login screen. I am also using the NextJs middleware to ensure a valid session is present.
Here is my[...nextauth].ts file
export default NextAuth({
providers: [
CredentialsProvider({
id: 'credentials',
name: 'credentials',
credentials: {
username: { label: 'Username', type: 'text', placeholder: '' },
password: { label: 'Password', type: 'password' },
},
async authorize(credentials, req) {
console.log(
`Authorize Function called with creds - ${JSON.stringify(
credentials
)}`
)
//Developement Authentication
if (process.env.RUNNING_ENV == 'DEVELOPMENT')
return {
id: process.env.DEV_USER as string,
group: process.env.DEV_GROUP as string,
}
// const { username, password } = credentials
const username = credentials?.username
const password = credentials?.password
if (!username || !password) {
throw new Error('Enter Username and Password')
}
try {
const response = await authenticate(username, password)
//console.log(`Response: ${JSON.stringify(response)}`)
return { username, group: 'cyberlab' }
} catch (err) {
//console.log(`Error authenticating: ${err}`)
}
return null
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
pages: {
signIn: '/login',
},
//Specifies JSON web tokens will be used.
jwt: {
secret: process.env.JWT_SECRET,
async encode({ token, secret, maxAge }) {
console.log(`Before Encode function token: ${JSON.stringify(token)}`)
const jwtClaims = {
sub: token.username,
name: token.username,
iat: Date.now() / 1000,
exp: Math.floor(Date.now() / 1000) + 3600,
hasura: {
'x-hasura-allowed-roles': [
'CRU',
'Forensic',
'EvSpecialist',
'Preview',
'dany',
],
'x-hasura-default-role': 'dany',
'x-hasura-user-id': token.username,
},
}
const encodedToken = jwt.sign(jwtClaims, secret, {
algorithm: 'HS512',
})
console.log(`Encoded Token: ${JSON.stringify(encodedToken)}`)
return encodedToken
},
async decode({ token, secret }) {
const decodedToken = jwt.verify(token, secret, {
algorithms: ['HS512'],
})
//console.log(`Decoded Token: ${JSON.stringify(decodedToken)}`)
return decodedToken
},
},
callbacks: {
async jwt({ token, user }) {
console.log(`Callback token: ${JSON.stringify(token)}`)
console.log(`Callback user: ${JSON.stringify(user)}`)
// * Hasura required claims will be added here
// TODO check to see if hasura claims exist, if not assign them else just pass the token.
if (user) {
console.log(`Adding User in callback`)
token.username = user.id
token.group = user.group
// token.hasura = {
// 'x-hasura-allowed-roles': [
// 'cru',
// 'forensic',
// 'evSpecialist',
// 'preview',
// 'dany',
// ],
// 'x-hasura-default-role': 'dany',
// 'x-hasura-user-id': token.username,
// }
}
return token
},
async session({ session, token, user }) {
// session.type = token.type
session.name = token.username
session.user = user
session.group = token.group
//console.log(`Session in Callback: ${JSON.stringify(session)}`)
//console.log(`Session type in callback: ${session.type}`)
//console.log(`Session User in callback: ${session.name}`)
return session
},
},
session: {
//Sets the session to use JSON Web Token
strategy: 'jwt',
//Sets the max idle time before token expires in seconds - Currently 1hr
maxAge: 3600,
},
})
Here is my terminal output
Callback token: {"sub":"JDoe"}
Callback user: {"id":"JDoe","group":"DEVELOPMENT"}
Adding User in callback
Before Encode function token: {"sub":"JDoe","username":"JDoe","group":"DEVELOPMENT"}
Encoded Token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJKRG9lIiwibmFtZSI6IkpEb2UiLCJpYXQiOjE2NDYzMTg2NTMuODA4LCJleHAiOjE2NDYzMjIyNTMsImhhc3VyYSI6eyJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbIkNSVSIsIkZvcmVuc2ljIiwiRXZTcGVjaWFsaXN0IiwiUHJldmlldyIsImRhbnkiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoiZGFueSIsIngtaGFzdXJhLXVzZXItaWQiOiJKRG9lIn19.lMkiBj6eIKT0CH-6sullN3qO9pDZimKLNfsUSR6G8WUrdtK_DD1kmtmu_nmwpE-RWSkEwSQC-u3g-ocRtrSinQ"
Callback token: {"sub":"JDoe","name":"JDoe","iat":1646318653.808,"exp":1646322253,"hasura":{"x-hasura-allowed-roles":["CRU","Forensic","EvSpecialist","Preview","dany"],"x-hasura-default-role":"dany","x-hasura-user-id":"JDoe"}}
Callback user: undefined
Before Encode function token: {"sub":"JDoe","name":"JDoe","iat":1646318653.808,"exp":1646322253,"hasura":{"x-hasura-allowed-roles":["CRU","Forensic","EvSpecialist","Preview","dany"],"x-hasura-default-role":"dany","x-hasura-user-id":"JDoe"}}
Encoded Token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDYzMTg2NTMuODcyLCJleHAiOjE2NDYzMjIyNTMsImhhc3VyYSI6eyJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbIkNSVSIsIkZvcmVuc2ljIiwiRXZTcGVjaWFsaXN0IiwiUHJldmlldyIsImRhbnkiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoiZGFueSJ9fQ.lPao8tCFq7z0Pb8tIO7sm0L91fkwajA-Uuu_OgG6rIgo4sC3z6Zd07q1XaKNQ0P3-xt2c1bF0up6tVab3djG-g"
Callback token: {"sub":"JDoe","name":"JDoe","iat":1646318653.808,"exp":1646322253,"hasura":{"x-hasura-allowed-roles":["CRU","Forensic","EvSpecialist","Preview","dany"],"x-hasura-default-role":"dany","x-hasura-user-id":"JDoe"}}
Callback user: undefined
Before Encode function token: {"sub":"JDoe","name":"JDoe","iat":1646318653.808,"exp":1646322253,"hasura":{"x-hasura-allowed-roles":["CRU","Forensic","EvSpecialist","Preview","dany"],"x-hasura-default-role":"dany","x-hasura-user-id":"JDoe"}}
Encoded Token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDYzMTg2NTUuMjk3LCJleHAiOjE2NDYzMjIyNTUsImhhc3VyYSI6eyJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbIkNSVSIsIkZvcmVuc2ljIiwiRXZTcGVjaWFsaXN0IiwiUHJldmlldyIsImRhbnkiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoiZGFueSJ9fQ.wnaes7CejSGCjHTzwNKTsNV3pfhy4iYqyRmfaNhpELQvJDciYMfTyFc2O4byq8cLAP2brUpfDwyQZIFZNMAGPA"
Callback token: {"iat":1646318655.297,"exp":1646322255,"hasura":{"x-hasura-allowed-roles":["CRU","Forensic","EvSpecialist","Preview","dany"],"x-hasura-default-role":"dany"}}
Callback user: undefined
Before Encode function token: {"iat":1646318655.297,"exp":1646322255,"hasura":{"x-hasura-allowed-roles":["CRU","Forensic","EvSpecialist","Preview","dany"],"x-hasura-default-role":"dany"}}
Encoded Token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDYzMTg2NTUuNzQ3LCJleHAiOjE2NDYzMjIyNTUsImhhc3VyYSI6eyJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbIkNSVSIsIkZvcmVuc2ljIiwiRXZTcGVjaWFsaXN0IiwiUHJldmlldyIsImRhbnkiXSwieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoiZGFueSJ9fQ.6saQlySc7rgHikj85iejpz6nRm9fEtEdupz_j1hcwZ0TcZiQlLvY1-M-9xkju2F-0MlWmQwIj-bfEU7BmuEc5w"
Callback token: {"iat":1646318655.297,"exp":1646322255,"hasura":{"x-hasura-allowed-roles":["CRU","Forensic","EvSpecialist","Preview","dany"],"x-hasura-default-role":"dany"}}
Callback user: undefined
Before Encode function token: {"iat":1646318655.297,"exp":1646322255,"hasura":{"x-hasura-allowed-roles":["CRU","Forensic","EvSpecialist","Preview","dany"],"x-hasura-default-role":"dany"}}
Encoded Token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NDYzMTg2NTUuOTgsImV4cCI6MTY0NjMyMjI1NSwiaGFzdXJhIjp7IngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsiQ1JVIiwiRm9yZW5zaWMiLCJFdlNwZWNpYWxpc3QiLCJQcmV2aWV3IiwiZGFueSJdLCJ4LWhhc3VyYS1kZWZhdWx0LXJvbGUiOiJkYW55In19.0tqw2fg_GjQGY55sHCKxscipHaGj9IHrdgNKBETsOzMPn5Rai3CFx1TMJP9ZOcyAzbSBmrXN31uWKCrda37X_g"
Here is what my middleware currently looks like.
import { NextApiRequest } from 'next'
import { getToken } from 'next-auth/jwt'
import { NextResponse, NextRequest } from 'next/server'
//Groups and Pages Associated with groups
// const userGroups = [
// 'CRU',
// 'Forensic',
// 'Evidence Specialist',
// 'Preview',
// 'Dany',
// 'DEVELOPMENT',
// 'TEST',
// ]
//Group access pages.
const CRU_PAGES: string[] = ['']
const FORENSIC_PAGES: string[] = ['']
const EVSPEVIALIST_PAGES: string[] = ['']
const PREVIEW_PAGES: string[] = ['']
const DANY_PAGES: string[] = ['']
const DEV_PAGES: string[] = ['/testpages', '/testpage', '/cases']
const TEST_PAGES: string[] = ['/testpage']
//Default Access Routes
const DEFAULT_PAGES: string[] = [
'/',
'/unauthorized',
'/api',
'/favicon.ico',
'/login',
]
//The following function will protect the routes or redirect to unauthorized if not allowed to access a page.
function checkAuthPath(pathname: string, group: string, req: NextApiRequest) {
var authorizedPages: string[] = ['']
//Based on group assign authorized pages to the authorizedPages array.
switch (group) {
case 'CRU':
authorizedPages = CRU_PAGES
break
case 'Forensic':
authorizedPages = FORENSIC_PAGES
break
case 'Evidence Specialist':
authorizedPages = EVSPEVIALIST_PAGES
break
case 'Preview':
authorizedPages = PREVIEW_PAGES
break
case 'Dany':
authorizedPages = DANY_PAGES
break
case 'DEVELOPMENT':
authorizedPages = DEV_PAGES
break
default:
authorizedPages = TEST_PAGES
}
authorizedPages = authorizedPages.concat(DEFAULT_PAGES)
//Determine if request path is in the authorized paths Array. If not redirect to unauthorized.
if (authorizedPages.includes('/' + pathname.split('/')[3])) {
//console.log(`pathname in checkauth function: ${pathname}`)
//console.log('Authorized')
return NextResponse.next()
} else {
//console.log('Unauthorized')
return NextResponse.redirect(new URL('/unauthorized', req.url))
}
}
export async function middleware(req: NextApiRequest) {
const token = await getToken({ req, secret: process.env.JWT_SECRET })
// console.log(`Middleware Token: ${JSON.stringify(token)}`)
const pathname = req.url
if (typeof pathname === 'string') {
if (pathname.includes('/api/auth') || token) {
if (token) {
//console.log('Token Found')
//console.log(`pathname: ${pathname}`)
const group = token.group as string
return checkAuthPath(pathname, group, req)
} else return NextResponse.next()
}
if (
(!token && pathname !== 'http://localhost:3000/login') ||
pathname.includes('/api/auth')
) {
//&& !pathname.includes('/api/auth/signin'
//console.log('Token Not Found')
return NextResponse.redirect(new URL('/login', req.url))
}
} else {
throw new Error('Pathname Undefined')
}
}
As you can see by the last output i am missing then user and group from the JWT. Also why does it loop so many times?