evolved.io logotype

#next.js #nextauth.js #authentication #keycloak #fundamentals #security #oauth2 #openid-connect

NextAuth.js: Fundamentals and Security

Understanding fundamental concepts and security considerations is crucial for managing authentication with NextAuth.js and an identity provider like Keycloak.

Avatar
Dennis GaidelJanuary 28, 2024

NextAuth.js is a highly recommended library for managing authentication in Next.js applications. This library not only offers a diverse array of authentication providers but also equips both backend and frontend developers with essential tools to effectively handle various aspects of the authentication process.

For software engineers aiming to integrate NextAuth.js smoothly into their applications, it's crucial to understand a range of interconnected concepts that are integral to the authentication process, particularly when using NextAuth.js with an identity provider like Keycloak.

In this article, we will delve into these concepts and terminologies, examine important security considerations, and highlight anti-patterns that should be avoided to ensure a robust, production-grade application.

JSON Webtoken (JWT)

JWTs are commonly found in an OpenID Connect context, a protocol built on the OAuth 2.0 framework.

There's a lot to unpack here, but for now, let's zoom in on access tokens and refresh tokens:

  • Identity providers like Keycloak typically issue at least an access token and often a refresh token.
  • The access token, signed by the issuer, carries key information for authentication (and sometimes authorization, if it includes claims, roles, etc.).
  • A signed access token can't be revoked unless you add extra security layers. This is why JWTs have expiration times—post-expiry, they're no longer valid.
  • Once an access token expires, you can use a refresh token to quietly get a new access token, avoiding another sign-in hassle for the user.
  • This refresh token request goes to the identity provider (like Keycloak), which may refuse to issue a new token. Typically, refresh tokens have a longer life span.
  • However, if both the access token and the refresh token expire, the user has to sign in again, granting the app permission to act on their behalf.

A well-known practice is the use of JWTs as bearer tokens, which are typically inserted into the Authorization header of requests sent to backend services.

Action Points

Given these key insights, we can outline the following crucial steps for our implementation:

  • Redirect the user to sign in and obtain a valid access token.
  • Ensure the access token is stored and accessible across the app, enabling requests to carry a valid Authorization header.
  • Check the expiration time of the access token regularly.
  • Utilize the refresh token to renew the tokens when the access token expires and the refresh token remains valid.

Sessions

NextAuth.js and Keycloak each manage their own sessions, which have distinct lifetimes and serve different purposes within the authentication process.

Keycloak

  • Initiates a session upon user sign-in, setting a specific lifetime for it.
  • Issues tokens, including access and refresh tokens, within this session. The validity of these tokens is linked to the active state of the Keycloak session.
  • The expiration or invalidation of the Keycloak session affects the usability of tokens, such as a refresh token, even if it's technically still valid.

NextAuth.js

  • NextAuth.js establishes its own session mechanism, independent of Keycloak's session. This involves several components:
  • Session as a concept: The idea of a session in NextAuth.js is to maintain a continuous interaction state with the user across the application. This concept is key to providing a seamless user experience, preventing the need for repeated logins as the user navigates your application.
  • Session token/object/data: This is the actual session information accessible via getServerSideProps. It includes non-sensitive data tied to the user's identity, such as names and email addresses, and is used to personalize the user experience on the client side. (see NextAuth.js docs)
    • When using multiple identity providers for logging into the application, NextAuth.js’s session layer serves as an effective abstraction. It offers a unified interface that distinctly separates the authentication process from the user-facing session.
  • Session state in the cookie: NextAuth.js manages session state through a cookie sent to the user's browser.
    • This cookie contains an encrypted session token, ensuring that the session state is maintained securely as the user interacts with the application.

Action Points

  • The NextAuth.js needs to be updated or expired before the underlying Keycloak session expires.
  • Gracefully handle errors caused by session timeouts, even when using a technically valid refresh token.

Security Considerations

Understanding the following implementation details and security best practices of NextAuth.js will allow you to properly protect your application:

  • Secure Cookie Handling: NextAuth.js generates a secure cookie containing the encrypted session and other information, such as the JWT provided by the identity provider.

    • The cookie is encrypted using the NEXTAUTH_SECRET environment variable.
    • ⚠️ The NEXTAUTH_SECRET is crucial for encoding and decoding the encrypted cookie and must never be exposed to the client.
  • Safeguarding Session Data:

    • The session data accessible via getServerSideProps should contain only non-sensitive user information (e.g., names, email addresses).
    • ⚠️ Critical: Avoid including the JWT token or any data susceptible to XSS attacks in the session data sent to the client's browser.
  • Server-Side JWT Handling:

    • The JWT provided by the identity provider should be handled only server-side, safeguarding against potential security breaches.

Example: Extracting the Access Token

I believe this is not explained in the docs, so here is a short snippet to explain how to extract the JWTs provided by the identity provider from the cookies on the server side:

import { getToken } from 'next-auth/jwt'

const handler = async (req: NextApiRequest, res: NextApiResponse<GraphQLResponse | string>) => {
    // The secret is used to decode the cookie state and get the identity provider token(s) 
    const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET })

    if (!token) {
        res.status(401).send({ errors: [{ message: 'Unauthorized: Missing token.' }] })
        return
    }

    // The tokens are provided as plain base64 encoded strings. You still have to decode them.
    const { accessToken, refreshToken } = token
}

Decoding NextAuth.js Session Cookies in External Services

In cases where you need to interact with an external service that lacks access to the getToken() function provided by NextAuth.js, you have two options:

  1. Shared Encoding/Decoding Implementation: If you have control over the external service, you can implement a common strategy for encoding and decoding the session cookie. This shared implementation allows both your Next.js project and the external service to handle session cookies consistently. (see NextAuth.js docs)

  2. Creating a Proxy Endpoint: In situations where the external service specifically requires the JWT provided by the identity provider and does not accommodate the Next.js session cookie, a "proxy endpoint" within your Next.js project can be a solution. This endpoint would be responsible for:

    • Extracting the JWT from the session cookie.
    • Forwarding the request to the intended external service.
    • Setting the extracted JWT as an Authorization header for the outbound request.

Conclusion

To sum up, managing authentication in a Next.js application using NextAuth.js involves a careful consideration of various components to ensure seamless integration. There's a significant amount of configuration and understanding required to make all these moving parts work together flawlessly. Ultimately, NextAuth.js provides a versatile framework that caters to the diverse needs of modern authentication processes.

If you have any insights, experiences, or questions regarding this topic, feel free to shoot me a message and share your feedback 👍