Overview Auth0

Auth0 is a flexible, drop-in solution to add authentication and authorization services to applications. It helps teams and organizations avoid the cost, time, and risk that comes with building your solution to authenticate and authorize users.

You can connect any application written in any language to Auth0 and define the identity providers you want to use, or said: how you want your users to log in.

Based on your app's technology, choose one of our SDKs (or call our API), and hook it up to your app. Each time a user tries to authenticate, Auth0 will verify their identity and send the required information back to your app.

It fits a number of platforms, including social networks. So, the system is perfect if you need to add social authentication to apps.

Building Next.js app with Auth0

Next.js allows you to generate a standalone static application without the need for a Node.js server. When you run next build, the command will generate HTML files for each page that supports it. You can use this generated output to deploy your site to any static hosting service, such as Now, Amazon S3, or Netlify. This technique could be used to generate complete websites as static sites, like a company public front page, or when you're creating an "admin dashboard".

Once the "shell" has been served, the client-side will call the necessary APIs (carrying the user information), fetch user-specific content, and update the page.

This model has several advantages when it comes to hosting. Static hosting sites are battle-tested, inexpensive, but more importantly, they are extremely fast and play well with CDNs.

One thing that will be somewhat different is how we handle authentication. The model where a server is available can handle the interaction with Auth0 and create a session, but in this model, we don't have a backend. All of the work happens on the frontend.

image-20200610-125054

If your use case requires dynamic content or user-specific content, you will also need to deploy something else, like an API. This API won't run as part of your static hosting site, so this is where you'll be using a platform like AWS Lambda, Heroku, or Now to deploy it. The client-side will then talk to that API directly by providing an access_token, fetch the dynamic content, and enrich the page served by the static hosting platform.

image-20200610-132606

And this is very similar to how any single-page application is built, where the application doesn't have an actual "backend" but instead calls one or more APIs. You'll find a variety of examples in the community of how to sign in to this type of application:

The great thing provided by Auth0 is use-auth0-hooks which makes configuring application easy:

1import { Auth0Provider } from 'use-auth0-hooks';
2
3export default class Root extends App {
4  render () {
5    const { Component, pageProps } = this.props;
6    return (
7      <Auth0Provider
8        domain={'example-dev.auth0.com'}
9        clientId={'clientId'}
10        redirectUri={'http://localhost:3000/'}>
11          <Component {...pageProps} />
12      </Auth0Provider>
13    );
14  }
15}

Note:

domain: example-dev.auth0.com and clientId: clientId are generated in your Auth0 when you create an app using Auth0 dashboard. Auth0 dashboard is available when you create an account.

Now you can use React Hooks to retrieve the user and request an access token for one of your APIs.

The access_token is then sent along when you call your API, which the following example does through the useApi hook:

1import { useAuth } from 'use-auth0-hooks';
2
3export default function MyShows() {
4  const { isAuthenticated, isLoading, accessToken } = useAuth({
5    audience: 'https://api/tv-shows',
6    scope: 'read:shows'
7  });
8
9  if (!isAuthenticated) {
10    return (
11      <div>You must first sign in to access your subscriptions.</div>;
12    )
13  }
14
15  if (isLoading) {
16    return (
17      <div>Loading your user information...</div>
18    );
19  }
20
21   const { response, loading } = useApi(
22    `${process.env.API_BASE_URL}/api/my/shows`,
23    accessToken
24  );
25
26  if (loading) {
27    return (
28      <h1>Subscriptions for {user.email}<h1>
29      <div>Loading your subscriptions ...</div>
30    );
31  }
32
33  return (
34    <h1>Subscriptions for {user.email}<h1>
35    <div>You have subscribed to a total of {response && response.shows && response.shows.length} shows...</div>
36  );
37}

What did happen?

When using auth0-spa-js the user will sign in using the Authorization Code Grant with PKCE. Then, the user will be redirected to Auth0, handling all of the required authentication and authorization logic. After the user completes the authentication process with Auth0, the user is redirected to your application with an Authorization Code in the query string.

The client-side will exchange that code for an id_token and optionally an access_token (1,2). The access_token can then be used to call your API. When the access_token expires, the same flow will happen again under the covers, using an <iframe>. This "silent authentication" approach will keep working for as long as the user is signed in — as long as the user has a session in Auth0. When the user's session in Auth0 expires or a sign-out takes place, this call will fail, and the user will be required to sign in again.

image-20200610-135335

Next.js Serverless Deployment Model

Where Next.js shines is in the serverless deployment model, where every page and API route are deployed as separate serverless functions implemented using ZEIT Now or AWS Lambda.

The following diagram illustrates how this model works: Next.js pages and API routes are all running as separate serverless functions. When the browser tries to access the TV Shows page (1), a function will take care of rendering and serving the page, effectively performing server-side rendering. This function will also call any APIs needed to fetch the necessary data (2).

image-20200610-142601

If the entire site has already been loaded, all of the rendering happens on the client whenever you visit another page. At that point, all API calls are made directly from the browser. As you can see, this is where the line between the frontend and backend layers starts to become blurry.

image-20200610-143536

There are two specific types of the serverless model when it comes to authentication, depending on where you need the user to be available:

  1. Serverless With the User on the Frontend

  2. Serverless With the User on the Backend

Serverless With the User on the Frontend

Whenever a page needs to be rendered on the server-side or an API route is called, these calls will be executed in a serverless function. In this model, authentication takes place on the client-side:

  1. The user is redirected to Auth0

  2. When the user is successfully signed in, they will be redirected back to the application

  3. The client-side will complete the code exchange with Auth0 and retrieve the user's id_token and access_token, which will be stored in memory.

image-20200610-144754

Any page rendered by the serverless function will only be able to return content accessible by all users without needing any form of authentication. Then, when the page is loaded, some logic can be executed on the client-side, fetching user-specific content by calling API routes or calling other APIs.

image-20200610-144841

In the diagram above, you can see an example of how this could work:

  1. The /account page can be rendered by a serverless function (SSR).

  2. In turn, this serverless function also calls the /api/pricing-tiers API route, which returns the different subscription types available in the application (for example, Free, Developer, Enterprise). This is public information, so authentication is not required here.

  3. When the client-side is ready, it can now call the /api/billing-info API route and provide the user's access token. The client-side can then render content that is specific to the user

Only the client-side and the API routes are aware of the user, while the server-side rendering of pages could only render public content (which is perfectly fine for SEO purposes).

Serverless With the User on the Backend

The second flavor in this model is when the user is needed when the serverless function is rendering the page. When that happens, you can't just rely on client-side authentication.

image-20200610-152218

This diagram is similar to the one from the frontend model, except for a few subtle but significant differences:

  1. The /account page can be rendered by a serverless function (SSR), but the browser will send the session cookie along.

  2. This serverless function also calls the /api/pricing-tiers API route (nothing changes here).

  3. This serverless function can now call /api/billing-info by forwarding the session cookie, making the server-side rendering of user content possible.

In this example, the user's account page can completely be rendered on the server-side.

There is also the case where the site is already fully loaded, and the user navigates to the account page. In that case, the client-side can call the endpoints directly, and the cookie will automatically be provided to the API route:

  1. The client-side can call an API route that requires authentication (because the session cookie will automatically be provided by the browser)

  2. The client-side will also call API routes that don't require authentication

To accommodate this use case, there is a recently published early access version of @auth0/nextjs-auth0, which takes care of authentication in the serverless deployment model using the Authorization Code Grant.

To use the library, you'll start by initializing an instance of the SDK:

1import { initAuth0 } from '@auth0/nextjs-auth0';
2
3export default initAuth0({
4  domain: '<AUTH0_DOMAIN>',
5  clientId: '<AUTH0_CLIENT_ID>',
6  clientSecret: '<AUTH0_CLIENT_SECRET>',
7  scope: 'openid profile',
8  redirectUri: 'http://localhost:3000/api/callback',
9  postLogoutRedirectUri: 'http://localhost:3000/',
10  session: {
11    cookieSecret: 'some-very-very-very-very-very-very-very-very-long-secret',
12    cookieLifetime: 60 * 60 * 8
13  }
14});

Once an instance is created, you'll be adding a few API routes in your Next.js application, which will be handling all of the necessary logic. Here is an example of the Login handler:

1import auth0 from '../../utils/auth0';
2
3export default async function login(req, res) {
4  try {
5    await auth0.handleLogin(req, res);
6  } catch(error) {
7    res.status(error.status || 500).end(error.message)
8  }
9}

And that's it! You can now access the user on the server-side:

1Profile.getInitialProps = async ({ req, res }) => {
2  if (typeof window === 'undefined') {
3    const { user } = await auth0.getSession(req);
4    if (!user) {
5      res.writeHead(302, {
6        Location: '/api/login'
7      });
8      res.end();
9      return;
10    }
11
12      return { user }
13  }
14}

By implementing the Profile handler, you'll also have an endpoint that exposes the user's information to the client side. As such, you can call API routes within your Next.js application without having to worry about access tokens or any of that. This is possible because the user's session is stored in a cookie, sent along with every request your client makes to your API route.

1async componentDidMount() {
2  const res = await fetch('/api/me');
3  if (res.ok) {
4    this.setState({
5      session: await res.json()
6    })
7  }
8}

What exactly happens behind the curtains here?

When using nextjs-auth0, the user will sign in using the Authorization Code Grant. At a high level, the user will be redirected to Auth0 (1,2), handling all of the required authentication and authorization logic. The user is redirected back to your application with an Authorization Code in the query string (3).

image-20200610-155022

The serverless function will exchange (4) that code for an id_token and optionally an access_token and refresh_token. After the id_token has been validated, a session will be created and stored in an encrypted cookie (5). Each time a page is rendered (server-side), or an API route is called, the session cookie will be sent to the serverless functions, then access the session and any relevant user information.

Conclusion

In a short conclusion, I would like to say that Auth0 saves lots of your time, effort, money, and your customer’s time! It supports all technology stacks and has great quickstarts.

Auth0 is a secure and universal service that ensures authentication and authorization functionality!