0

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?

1

1 Answer 1

0

Solved by including the custom encoding and decoding in the middleware too.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.