Skip to content

Latest commit

 

History

History
578 lines (441 loc) · 18.4 KB

EXAMPLES.md

File metadata and controls

578 lines (441 loc) · 18.4 KB

Examples

See also the example app.

Create your own instance of the SDK

When you use the named exports, the SDK creates an instance of the SDK for you and configures it with the provided environment variables.

// These named exports create and manage their own instance of the SDK configured with
// the provided `AUTH0_*` environment variables
import {
  handleAuth,
  handleLogin,
  handleCallback,
  handleLogout,
  handleProfile,
  withApiAuthRequired,
  withPageAuthRequired,
  getSession,
  getAccessToken
} from '@auth0/nextjs-auth0';

However, there are various reasons why you might want to create and manage an instance of the SDK yourself:

  • You may want to create your own instance for testing
  • You may not want to use environment variables for the configuration of secrets (for example, to use CredStash or AWS's Key Management Service)
  • You may be using a custom session store and need to provide the configuration as code.

In this case you can use the initAuth0 method to create an instance.

// utils/auth0.js
import { initAuth0 } from '@auth0/nextjs-auth0';

export default initAuth0({
  secret: 'LONG_RANDOM_VALUE',
  issuerBaseURL: 'https://your-tenant.auth0.com',
  baseURL: 'http://localhost:3000',
  clientID: 'CLIENT_ID',
  clientSecret: 'CLIENT_SECRET'
});

Now rather than using the named exports, you can use the instance methods directly.

// pages/api/auth/[auth0].js
import auth0 from '../../../utils/auth0';

// Use the instance method
export default auth0.handleAuth();

Note: You should not use the instance methods in combination with the named exports, otherwise you will be creating multiple instances of the SDK. For example:

// DON'T Mix instance methods and named exports
import auth0 from '../../../utils/auth0';
import { handleLogin } from '@auth0/nextjs-auth0';

export default auth0.handleAuth({
  // <= instance method
  async login(req, res) {
    try {
      // `auth0.handleAuth` and `handleLogin` will be using separate instances
      // You should use `auth0.handleLogin` instead
      await handleLogin(req, res); // <= named export
    } catch (error) {
      res.status(error.status || 400).end(error.message);
    }
  }
});

Customize handlers behavior

Pass custom parameters to the auth handlers or add your own logging and error handling.

// pages/api/auth/[auth0].js
import { handleAuth, handleLogin, handleProfile } from '@auth0/nextjs-auth0';
import { myCustomLogger, myCustomErrorReporter } from '../utils';

export default handleAuth({
  async login(req, res) {
    // Add your own custom logger
    myCustomLogger('Logging in');
    // Pass custom parameters to login
    await handleLogin(req, res, {
      authorizationParams: {
        custom_param: 'custom'
      },
      returnTo: '/custom-page'
    });
  },
  invite: handleLogin({
    authorizationParams: {
      invitation: req.query.invitation
    }
  }),
  'login-with-google': handleLogin({ authorizationParams: { connection: 'google' } }),
  'refresh-profile': handleProfile({ refetch: true }),
  onError(req, res, error) {
    // Add your own custom error handling
    myCustomErrorReporter(error);
    res.status(error.status || 400).end();
  }
});

Use custom auth urls

Instead of (or in addition to) creating /pages/api/auth/[auth0].js to handle all requests, you can create them individually at different urls.

Eg for login:

// api/custom-login.js
import { handleLogin } from '@auth0/nextjs-auth0';

export default async function login(req, res) {
  try {
    await handleLogin(req, res);
  } catch (error) {
    res.status(error.status || 400).end(error.message);
  }
}
// components/login-button.js
export default () => <a href="/api/custom-login">Login</a>;

Note: If you customize the login url you will need to set the environment variable NEXT_PUBLIC_AUTH0_LOGIN to this custom value for withPageAuthRequired to work correctly. And if you customize the profile url, you will need to set the NEXT_PUBLIC_AUTH0_PROFILE environment variable to this custom value for the useUser hook to work properly.

Protecting a Server-Side Rendered (SSR) Page

Page Router

Requests to /pages/profile without a valid session cookie will be redirected to the login page.

// pages/profile.js
import { withPageAuthRequired } from '@auth0/nextjs-auth0';

export default function Profile({ user }) {
  return <div>Hello {user.name}</div>;
}

// You can optionally pass your own `getServerSideProps` function into
// `withPageAuthRequired` and the props will be merged with the `user` prop
export const getServerSideProps = withPageAuthRequired();

See a running example of an SSR protected page in the example app or refer to the full list of configuration options for withPageAuthRequired here.

App Router

Requests to /profile without a valid session cookie will be redirected to the login page.

// app/profile/page.js
import { withPageAuthRequired, getSession } from '@auth0/nextjs-auth0';

export default withPageAuthRequired(async function Profile() {
  const { user } = await getSession();
  return <div>Hello {user.name}</div>;
}, { returnTo: '/profile' })
// You need to provide a `returnTo` since Server Components aren't aware of the page's URL

See a running example of a protected server component page in the example app or more info in the docs.

Protecting a Client-Side Rendered (CSR) Page

Page Router

Requests to /pages/profile without a valid session cookie will be redirected to the login page.

// pages/profile.js
import { withPageAuthRequired } from '@auth0/nextjs-auth0/client';

export default withPageAuthRequired(function Profile({ user }) {
  return <div>Hello {user.name}</div>;
});

See a running example of a CSR protected page in the example app.

Protect an API Route

Page Router

Requests to /api/protected without a valid session cookie will fail with 401.

// pages/api/protected.js
import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0';

export default withApiAuthRequired(async function myApiRoute(req, res) {
  const { user } = await getSession(req, res);
  res.json({ protected: 'My Secret', id: user.sub });
});

Then you can access your API from the frontend with a valid session cookie.

// pages/products
import useSWR from 'swr';
import { withPageAuthRequired } from '@auth0/nextjs-auth0/client';

const fetcher = async (uri) => {
  const response = await fetch(uri);
  return response.json();
};

export default withPageAuthRequired(function Products() {
  const { data, error } = useSWR('/api/protected', fetcher);
  if (error) return <div>oops... {error.message}</div>;
  if (data === undefined) return <div>Loading...</div>;
  return <div>{data.protected}</div>;
});

See a running example in the example app, the protected API route and the frontend code to access the protected API.

App Router

Requests to /api/protected without a valid session cookie will fail with 401.

// app/api/protected/route.js
import { withApiAuthRequired, getSession } from '@auth0/nextjs-auth0';

export const GET = withApiAuthRequired(async function myApiRoute(req) {
  const res = new NextResponse();
  const { user } = await getSession(req, res);
  return NextResponse.json({ protected: 'My Secret', id: user.sub }, res);
});

Then you can access your API from the frontend with a valid session cookie.

// app/products/page.jsx
'use client'
import useSWR from 'swr';
import { withPageAuthRequired } from '@auth0/nextjs-auth0/client';

const fetcher = async (uri) => {
  const response = await fetch(uri);
  return response.json();
};

export default withPageAuthRequired(function Products() {
  const { data, error } = useSWR('/api/protected', fetcher);
  if (error) return <div>oops... {error.message}</div>;
  if (data === undefined) return <div>Loading...</div>;
  return <div>{data.protected}</div>;
});

See a running example in the example app, the protected API route and the frontend code to access the protected API.

Protecting pages with Middleware

Protect your pages with Next.js Middleware.

To protect all your routes:

// middleware.js
import { withMiddlewareAuthRequired } from '@auth0/nextjs-auth0/edge';

export default withMiddlewareAuthRequired();

To protect specific routes:

// middleware.js
import { withMiddlewareAuthRequired } from '@auth0/nextjs-auth0/edge';

export default withMiddlewareAuthRequired();

export const config = {
  matcher: '/about/:path*'
};

For more info see: https://nextjs.org/docs/advanced-features/middleware#matching-paths

To run custom middleware for authenticated users:

// middleware.js
import { withMiddlewareAuthRequired, getSession } from '@auth0/nextjs-auth0/edge';

export default withMiddlewareAuthRequired(async function middleware(req) {
  const res = NextResponse.next();
  const user = await getSession(req, res);
  res.cookies.set('hl', user.language);
  return res;
});

For using middleware with your own instance of the SDK:

// middleware.js
import {
  initAuth0 // note the edge runtime specific `initAuth0`
} from '@auth0/nextjs-auth0/edge';

const auth0 = initAuth0({ ... });

export default auth0.withMiddlewareAuthRequired(async function middleware(req) {
  const res = NextResponse.next();
  const user = await auth0.getSession(req, res);
  res.cookies.set('hl', user.language);
  return res;
});

Access an External API from an API Route

Get an access token by providing your API's audience and scopes. You can pass them directly to the handlelogin method, or use environment variables instead.

// pages/api/auth/[auth0].js
import { handleAuth, handleLogin } from '@auth0/nextjs-auth0';

export default handleAuth({
  login: handleLogin({
    authorizationParams: {
      audience: 'https://api.example.com/products', // or AUTH0_AUDIENCE
      // Add the `offline_access` scope to also get a Refresh Token
      scope: 'openid profile email read:products' // or AUTH0_SCOPE
    }
  })
});

Use the session to protect your API route and the access token to protect your external API. The API route serves as a proxy between your front end and the external API.

// pages/api/products.js
import { getAccessToken, withApiAuthRequired } from '@auth0/nextjs-auth0';

export default withApiAuthRequired(async function products(req, res) {
  // If your access token is expired and you have a refresh token
  // `getAccessToken` will fetch you a new one using the `refresh_token` grant
  const { accessToken } = await getAccessToken(req, res, {
    scopes: ['read:products']
  });
  const response = await fetch('https://api.example.com/products', {
    headers: {
      Authorization: `Bearer ${accessToken}`
    }
  });
  const products = await response.json();
  res.status(200).json(products);
});

Getting a Refresh Token

  • Include the offline_access scope your configuration (or AUTH0_SCOPE)
  • Check "Allow Offline Access" in your API Settings
  • Make sure the "Refresh Token" grant is enabled in your Application Settings (this is the default)

Add a signup handler

Pass a custom authorize parameter to the login handler in a custom route.

If you are using the New Universal Login Experience you can pass the screen_hint parameter.

// pages/api/auth/[auth0].js
import { handleAuth, handleLogin } from '@auth0/nextjs-auth0';

export default handleAuth({
  signup: handleLogin({ authorizationParams: { screen_hint: 'signup' } })
});

If you are using the Classic Universal Login, in addition to the above change, you will also need to edit the Custom Login Page to set the initialScreen option inside the <script> tag:

var isSignup = config.extraParams && config.extraParams.screen_hint === "signup";

var lock = new Auth0Lock(config.clientID, config.auth0Domain, {
  // [...] // all other Lock options
  // use the value obtained to decide the first screen
  initialScreen: isSignup ? "signUp" : "login",
});

Users can then sign up using the signup handler.

<a href="/api/auth/signup">Sign up</a>

Use with Base Path and Internationalized Routing

With Next.js you can deploy a Next.js application under a sub-path of a domain using Base Path and serve internationalized (i18n) routes using Internationalized Routing.

If you use these features the urls of your application will change and so the urls to the nextjs-auth0 routes will change. To accommodate this there are various places in the SDK that you can customise the url.

For example if basePath: '/foo' you should prepend this to the loginUrl and profileUrl specified in your Auth0Provider

// _app.jsx
function App({ Component, pageProps }) {
  return (
    <UserProvider loginUrl="/foo/api/auth/login" profileUrl="/foo/api/auth/me">
      <Component {...pageProps} />
    </UserProvider>
  );
}

Also, any links to login or logout should include the basePath:

<a href="/foo/api/auth/login">Login</a><br />
<a href="/foo/api/auth/logout">Logout</a>

You should configure baseUrl (or the AUTH0_BASE_URL environment variable) eg

# .env.local
AUTH0_BASE_URL=http://localhost:3000/foo

For any pages that are protected with the Server Side withPageAuthRequired you should update the returnTo parameter depending on the basePath and locale if necessary.

// ./pages/my-ssr-page.jsx
export default MySsrPage = () => <></>;

const getFullReturnTo = (ctx) => {
  // TODO: implement getFullReturnTo based on the ctx.resolvedUrl, ctx.locale
  // and your next.config.js's basePath and i18n settings.
  return '/foo/en-US/my-ssr-page';
};

export const getServerSideProps = (ctx) => {
  const returnTo = getFullReturnTo(ctx.req);
  return withPageAuthRequired({ returnTo })(ctx);
};

Use a custom session store

You need to create your own instance of the SDK in code, so you can pass an instance of your session store to the SDK's configuration.

// lib/auth0.ts
import { SessionStore, SessionStorePayload, initAuth0 } from '@auth0/nextjs-auth0';

class Store implements SessionStore {
  private store: KeyValueStoreLikeRedis<SessionStorePayload>;
  constructor() {
    // If you set the expiry accross the whole store use the session config,
    // for example `min(config.session.rollingDuration, config.session.absoluteDuration)`
    // the default is 24 hrs
    this.store = new KeyValueStoreLikeRedis();
  }
  async get(id) {
    const val = await this.store.get(id);
    return val;
  }
  async set(id, val) {
    // To set the expiry per item, use `val.header.exp` (in secs)
    const expiryMs = val.header.exp * 1000;
    // Example for Redis: redis.set(id, val, { pxat: expiryMs });
    await this.store.set(id, val);
  }
  async delete(id) {
    await this.store.delete(id);
  }
}

export default initAuth0({
  session: {
    store: new Store()
  }
});

Then use your instance wherever you use the server methods of the SDK.

// /pages/api/auth/[auth0].js
import auth0 from '../../../lib/auth0';

export default auth0.handleAuth();

Back-Channel Logout

Back-Channel Logout requires a session store, so you'll need to create your own instance of the SDK in code and pass an instance of your session store to the SDK's configuration:

// lib/auth0.ts
import { initAuth0 } from '@auth0/nextjs-auth0';

export default initAuth0({
  backChannelLogout: {
    store: new Store() // See "Use a custom session store" for how to define a Store class.
  }
});

If you are already using a session store, you can just reuse that:

// lib/auth0.ts
import { initAuth0 } from '@auth0/nextjs-auth0';

export default initAuth0({
  session: {
    store: new Store()
  },
  backchannelLogout: true
});

Once you've enabled the backchannelLogout option, handleAuth will create a /api/auth/backchannel-logout POST handler.

Pages Router

// /pages/api/auth/[auth0].js
import auth0 from '../../../lib/auth0';

export default auth0.handleAuth();

App Router

// /app/api/auth/[auth0]/route.js
import auth0 from '../../../lib/auth0';

const handler = auth0.handleAuth();

// For Back-Channel Logout you need to export a GET and a POST handler.
export { handler as GET, handler as POST };

Then configure your tenant following these instructions. Your "OpenID Connect Back-Channel Logout URI" will be {YOUR_AUTH0_BASE_URL}/api/auth/backchannel-logout.