Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement authentication in Next.js

I am new to Next.js and I am struggling with the authentication system using jwt token. I want to know what is the best / standard way to store the jwt token and routing with the authentication system. I have been trying different approaches, from different tutorials/articles, but do not quite understand it. Here are what I have tried.

  1. When the user login, it sends username/password to a separated API server (ex. new project that handles backend stuff), the server will respond with the access-token, then in Next.js project, I set the cookie with that received token. In Next.js project, protected routes will be wrapped with a withAuth hoc, which will check for the token in a cookie. The problem with this approach is that it is vulnerable to XSS because the cookie has no httpOnly flag.

  2. This is similar to 1.) but using localStorage, the problem is access-token could not be sent to the server on the first request. (This one I'm not sure, but in my understanding, in every HTTP request, I must stick my access-token manually, so what about requests that I have no control over? ex. first request or using <a> tag).

  3. I wrote authentication backend inside Next.js server (custom express server). When the user login, the server will validate it and then set an httpOnly cookie. Then the problem is, with client-side routing (go to URL using Next.js Router), it could not check for token. For example, if a page is wrapped with withAuth hoc, but it cannot access the token inside cookies with javascript.

And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage, then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to the server in every client-side page change?

like image 200
Kongpon Charanwattanakit Avatar asked Apr 19 '18 11:04

Kongpon Charanwattanakit


People also ask

How do I authenticate Nextjs app?

We can authenticate a Nextjs app using the set of features it has. What we will do is that when a route in Nextjs is navigated to we will check the user to verify its credentials, if not successful we will redirect the user to a page, if it is successful we proceed to display the page.

How do I use firebase authentication in Nextjs?

Firebase configuration for authentication To integrate our Firebase project into our Next. js project, go to Project settings and find “SDK setup and configuration”, select radio Config. We will need to copy the snippet that has to be added to our project code.

Can I use ID in Nextjs?

Next. js allows you to create pages with dynamic routes. For example, you can create a file called pages/posts/[id]. js to show a single blog post based on id .

How do I use JavaScript authentication?

To setup Authentication, the user needs to configure OAuth 2.0 ID in JavaScript and the backend code. 5. JavaScript application uses client ID to obtain the Google ID token from OAuth 2.0 server and send ID in the request.


2 Answers

Since we are on quarantine I have enough time to answer this question. It will be a long answer.

Next.js uses the App component to initialize the pages. _app page is responsible for rendering our pages. We authenticate users on _app.js because anything that we return from getInitialProps can be accessed by all of the other pages. We authenticate user here, authentication decision will be passed to pages, from pages to header, so each page can decide if the user is authenticated or not. (Note that it could be done with redux without prop drilling but it would make the answer more complex)

  static async getInitialProps({ Component, router, ctx }) {     let pageProps = {};     const user = process.browser       ? await auth0.clientAuth()       : await auth0.serverAuth(ctx.req); // I explain down below      //this will be sent to all the components     const auth = { user, isAuthenticated: !!user };     if (Component.getInitialProps) {       pageProps = await Component.getInitialProps(ctx);     }      return { pageProps, auth };   }    render() {     const { Component, pageProps, auth } = this.props;     return <Component {...pageProps} auth={auth} />;   } } 

If we are on the browser and need to check if a user is authenticated, we just retrieve the cookie from the browser, which is easy. But we always have to verify the token. It is the same process used by browser and server. I will explain down below. But if we are on the server. we have no access to the cookies in the browser. But we can read from the "req" object because cookies are attached to the req.header.cookie. this is how we access to cookies on the server.

async serverAuth(req) {     // console.log(req.headers.cookie) to check     if (req.headers.cookie) {       const token = getCookieFromReq(req, "jwt");       const verifiedToken = await this.verifyToken(token);       return verifiedToken;     }     return undefined;   } 

here is getCookieFromReq(). remember we have to think functional.

const getCookieFromReq = (req, cookieKey) => {   const cookie = req.headers.cookie     .split(";")     .find((c) => c.trim().startsWith(`${cookieKey}=`));    if (!cookie) return undefined;   return cookie.split("=")[1]; }; 

Once we get the cookie, we have to decode it, extract the expiration time to see if it is valid or not. this part is easy. Another thing we have to check is if the signature of the jwt is valid. Symmetric or asymmetric algorithms are used to sign the jwt. You have to use private keys to validate the signature of symmetric algorithms. RS256 is the default asymmetric algorithms for APIs. Servers that use RS256, provide you with a link to get jwt to use the keys to validate the signature. You can either use [jwks-rsa][1] or you can do on your own. You have to create a certificate and then verify if the token is valid.

Assume that our user authenticated now. You said, "And I've seen a lot of people, in getInitialProps of the protected route, they only check for existence token in cookie / localStorage,". We use protected routes to give access only to the authorized users. In order to access those routes, users have to show their jwt tokens and express.js uses middlewares to check if the user's token is valid. Since you have seen a lot of examples, I will skip this part.

"then what if the token is being revoked or blacklisted, how do they handle it because they did not send the token to the server? Or do I have to send the token to a server in every client-side page changing?"

with verifying token process we are 100% sure if the token is valid or not. When a client asks the server to access some secret data, the client has to send the token to the server. Imagine when you mount the component, component asks the server to get some data from the protected routes. The server will extract the req object, take the jwt and use it to fetch data from the protected routes. Implementation of the fetching data for browser and server are different. And if the browser makes a request, it just needs the relative path but the server needs an absolute path. As you should know fetching data is done getInitialProps() of the component and this function executed on both client and server. here is how you should implement it. I just attached the getInitialProps() part.

MyComponent.getInitialProps = async (ctx) => {   const another = await getSecretData(ctx.req);  //reuslt of fetching data is passed to component as props   return { superValue: another }; };        const getCookieFromReq = (req, cookieKey) => {       const cookie = req.headers.cookie         .split(";")         .find((c) => c.trim().startsWith(`${cookieKey}=`));        if (!cookie) return undefined;       return cookie.split("=")[1];     };          const setAuthHeader = (req) => {       const token = req ? getCookieFromReq(req, "jwt") : Cookies.getJSON("jwt");        if (token) {         return {           headers: { authorization: `Bearer ${token}` },         };       }       return undefined;     };           export const getSecretData = async (req) => {       const url = req ? "http://localhost:3000/api/v1/secret" : "/api/v1/secret";       return await axios.get(url, setAuthHeader(req)).then((res) => res.data);     };      [1]: https://www.npmjs.com/package/jwks-rsa 
like image 94
Yilmaz Avatar answered Oct 04 '22 14:10

Yilmaz


With the introduction of Next.JS v8, there are examples placed in the NextJS example page. The basic idea to follow is:

JWT

  • Using cookies to store the token (you may choose to further encrypt it or not)
  • Sending the cookies as authorization headers

OAuth

  • Using a third-party authentication service such as OAuth2.0
  • Using Passport
like image 24
cr05s19xx Avatar answered Oct 04 '22 14:10

cr05s19xx