├── 01-Login
├── .eslintrc.json
├── .gitignore
├── EXAMPLE.env
├── README.md
├── components
│ └── banner.js
├── next.config.js
├── package.json
├── pages
│ ├── _app.js
│ ├── dashboard.js
│ └── index.js
├── public
│ ├── favicon.ico
│ └── passageloginscreen.png
└── styles
│ ├── App.module.css
│ ├── Banner.module.css
│ └── globals.css
├── 02-Login-With-Profile
├── .eslintrc.json
├── .gitignore
├── EXAMPLE.env
├── README.md
├── components
│ └── banner.js
├── next.config.js
├── package.json
├── pages
│ ├── _app.js
│ ├── dashboard.js
│ └── index.js
├── public
│ └── favicon.ico
└── styles
│ ├── App.module.css
│ ├── Banner.module.css
│ └── globals.css
├── 03-Login-app-directory
├── .env.example
├── .eslintrc.json
├── .gitignore
├── README.md
├── actions
│ └── getCurrentUserInfo.ts
├── app
│ ├── about
│ │ └── page.tsx
│ ├── blog
│ │ ├── [slug]
│ │ │ └── page.tsx
│ │ └── page.tsx
│ ├── dashboard
│ │ └── page.tsx
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components
│ ├── Auth.tsx
│ ├── DashboardContent.tsx
│ ├── LogoutButton.tsx
│ ├── banner.tsx
│ └── footer.tsx
├── middleware.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│ ├── logo.svg
│ ├── next.svg
│ └── vercel.svg
├── tailwind.config.js
├── tsconfig.json
└── typings.d.ts
└── README.md
/01-Login/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "semi": ["warn", "always"],
5 | "semi-style": ["warn", "last"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/01-Login/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | # DS_Store file
107 | .DS_Store
108 |
109 | # package-lock
110 | package-lock.json
--------------------------------------------------------------------------------
/01-Login/EXAMPLE.env:
--------------------------------------------------------------------------------
1 | PASSAGE_APP_ID=
2 | # Note that because this example uses the passage-node package, an API key is required.
3 | # You can retrieve an API key for your application by going to the Dashboard and selecting Settings, then API Keys.
4 | PASSAGE_API_KEY=
--------------------------------------------------------------------------------
/01-Login/README.md:
--------------------------------------------------------------------------------
1 | # Passage Next.js Example App
2 |
3 | This example application uses the [Passage Auth Element](https://www.npmjs.com/package/@passageidentity/passage-elements) in a Next.js application to authenticate users using biometrics or magic links.
4 |
5 | [Passage Node.js SDK](https://www.npmjs.com/package/@passageidentity/passage-node) is used to verify users on authenticated endpoints. To run this example application, follow the instructions below.
6 |
7 | ## Configure Your Environment Variables
8 |
9 | 1. Copy the text in the `EXAMPLE.env` file to your own `.env` file.
10 | 2. Replace the example variables with your own Passage App ID and API Key. You can get these from the [Passage Console](https://console.passage.id). You'll have to register and login, and then create a new application. (Note that you'll use Passage to do so.)
11 | 3. The App ID is found on the main Dashboard page, and the API Key can be created and retrieved from the `Setting/API Keys` page. **Note that both are required.**
12 |
13 |
14 | ## Getting Started
15 |
16 | First, run the development server:
17 |
18 | ```bash
19 | npm run dev
20 | # or
21 | yarn dev
22 | ```
23 |
24 | Then, ensure that all the dependencies are properly installed by running:
25 |
26 | ```bash
27 | npm install
28 | ```
29 |
30 | Once that is complete, open a browser and navigate to [http://localhost:3000](http://localhost:3000) to see the result.
31 |
32 | 
33 |
34 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
35 |
36 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
37 |
38 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
39 |
40 | # Using Passage with Next.js
41 |
42 | ## Importing and Using the Passage-Auth Custom Element
43 |
44 | The easiest way to add authentication to a web frontend is with a Passage Auth custom element. First you'll need to install the [passage-elements](https://www.npmjs.com/package/@passageidentity/passage-elements) package from npm:
45 |
46 | ```bash
47 | npm i --save @passageidentity/passage-elements
48 | ```
49 | Importing `@passageidentity/passage-elements/passage-auth` triggers a side-effect that will register the passage-auth custom element with the client browser for usage. Since Next.js pre-renders pages on the server this presents a common issue with using web components, such as the Passage elements, in pre-rendered pages - when the server side pre-render occurs there is no client window defined to call `window.customElements.define()` on, which results in an error being thrown.
50 |
51 | The most common solution when using custom elements in pre-rendered applications is to defer the registration of the custom element to a lifecycle hook so that the code is only executed when the client app is executed in browser. This is done in this example in [pages/index.js](https://github.com/passageidentity/example-nextjs/blob/main/pages/index.js):
52 |
53 | ```javascript
54 | export default function Home() {
55 |
56 | useEffect(()=>{
57 | require('@passageidentity/passage-elements/passage-auth');
58 | }, []);
59 |
60 | return (
61 |
62 | ...
63 |
64 | )
65 | }
66 | ```
67 |
68 | ## Getting Authentication Status and User Information with Server-Side Rendering
69 |
70 | After the user has logged in with Passage, all requests need to be authenticated using the JWT provided by Passage. Use the [Passage Node.js SDK](https://www.npmjs.com/package/@passageidentity/passage-node) to authenticate requests and retrieve user data for your application.
71 |
72 | In this example, we handle authentication securely in Next.js's server-side rendering function [`getServerSideProps()`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering). Per Next.js documention you can import modules in top-level scope for use in `getServerSideProps`. Imports used in `getServerSideProps` will not be bundled for the client-side. This means you can write server-side code directly in `getServerSideProps`.
73 |
74 | The JWT provided by Passage is stored in both cookies and localstorage. Next.js provides the cookies set for an application to `getServerSideProps` which allows passing the JWT from the client browser to the server to handle authentication.
75 |
76 | This is done in this example in pages/dashboard.js.
77 |
--------------------------------------------------------------------------------
/01-Login/components/banner.js:
--------------------------------------------------------------------------------
1 | import styles from '../styles/Banner.module.css';
2 |
3 | export default function Banner() {
4 | return (
5 |
16 | >
17 | );
18 | }
19 |
20 | export default MyApp;
--------------------------------------------------------------------------------
/01-Login/pages/dashboard.js:
--------------------------------------------------------------------------------
1 | // this import is only included in the server build since its only used in getServerSideProps
2 | import Passage from '@passageidentity/passage-node';
3 | import styles from '../styles/App.module.css'
4 |
5 | function Dashboard({isAuthorized, username}){
6 |
7 | const authorizedBody =
8 | <>
9 | You successfully signed in with Passage.
10 |
11 | Your username is: {username}
12 | >
13 |
14 | const unauthorizedBody =
15 | <>
16 | You have not logged in and cannot view the dashboard.
17 |
28 | );
29 | };
30 |
31 | export async function getServerSideProps(context) {
32 | // getServerSideProps runs server-side only and will never execute on the client browser
33 | // this allows the safe use of a private Passage API Key
34 | const passage = new Passage({
35 | appID: process.env.PASSAGE_APP_ID,
36 | apiKey: process.env.PASSAGE_API_KEY,
37 | authStrategy: "HEADER",
38 | });
39 | try {
40 | const authToken = context.req.cookies['psg_auth_token'];
41 | const req = {
42 | headers: {
43 | authorization: `Bearer ${authToken}`,
44 | },
45 | };
46 | const userID = await passage.authenticateRequest(req);
47 | if (userID) {
48 | // user is authenticated
49 | const { email, phone } = await passage.user.get(userID);
50 | const identifier = email ? email : phone;
51 | return { props: {isAuthorized: true, username: identifier} };
52 | }
53 | } catch (error) {
54 | // authentication failed
55 | return { props: {isAuthorized: false, username: ''} };
56 | }
57 | }
58 |
59 | export default Dashboard;
--------------------------------------------------------------------------------
/01-Login/pages/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | export default function Home({appID}) {
4 | useEffect(()=>{
5 | require('@passageidentity/passage-elements/passage-auth');
6 | }, []);
7 |
8 | return (
9 | <>
10 |
11 | >
12 | )
13 | }
14 |
15 | export async function getStaticProps(){
16 | return {
17 | props: {
18 | appID: process.env.PASSAGE_APP_ID
19 | }
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/01-Login/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/passageidentity/example-nextjs/722949a74370f5d0842b67b34b21ecfcb0b32fa9/01-Login/public/favicon.ico
--------------------------------------------------------------------------------
/01-Login/public/passageloginscreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/passageidentity/example-nextjs/722949a74370f5d0842b67b34b21ecfcb0b32fa9/01-Login/public/passageloginscreen.png
--------------------------------------------------------------------------------
/01-Login/styles/App.module.css:
--------------------------------------------------------------------------------
1 |
2 | .mainContainer {
3 | background: white;
4 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
5 | border-radius: 20px;
6 | width: 310px;
7 | min-height: 310px;
8 | margin: 30px auto;
9 | }
10 | .footer {
11 | text-align: center;
12 | font-size: 18px;
13 | }
14 |
15 | .dashboard{
16 | padding: 30px 30px 20px;
17 | }
18 | .title {
19 | font-size: 24px;
20 | font-weight: 700;
21 | margin-bottom: 30px;
22 | }
23 | .message {
24 | overflow-wrap: anywhere;
25 | }
26 | .link {
27 | color: black;
28 | text-decoration-color: black;
29 | }
30 |
--------------------------------------------------------------------------------
/01-Login/styles/Banner.module.css:
--------------------------------------------------------------------------------
1 | .mainHeader{
2 | padding: 20px 30px;
3 | display: flex;
4 | align-items: center;
5 | background-color: #282727;
6 | color: white;
7 | }
8 | .headerText {
9 | font-size: 24px;
10 | margin: 0px 10px;
11 | }
12 |
13 | .passageLogo {
14 | background-image: url('https://storage.googleapis.com/passage-docs/passage-logo-dark.svg');
15 | background-repeat: no-repeat;
16 | width: 60px;
17 | height: 60px;
18 | cursor: pointer;
19 | }
20 | .spacer {
21 | flex-grow: 1;
22 | }
23 |
24 | .link {
25 | margin-left: 20px;
26 | color: white;
27 | text-decoration: underline;
28 | text-decoration-color: white;
29 | }
--------------------------------------------------------------------------------
/01-Login/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | background-color: #E5E5E5;
8 | }
9 |
10 | a {
11 | color: inherit;
12 | text-decoration: none;
13 | }
14 |
15 | * {
16 | box-sizing: border-box;
17 | }
18 |
--------------------------------------------------------------------------------
/02-Login-With-Profile/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals",
3 | "rules": {
4 | "semi": ["warn", "always"],
5 | "semi-style": ["warn", "last"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/02-Login-With-Profile/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
106 | # DS_Store file
107 | .DS_Store
108 |
109 | # package-lock
110 | package-lock.json
--------------------------------------------------------------------------------
/02-Login-With-Profile/EXAMPLE.env:
--------------------------------------------------------------------------------
1 | PASSAGE_APP_ID=
2 | # Note that because this example uses the passage-node package, an API key is required.
3 | # You can retrieve an API key for your application by going to the Dashboard and selecting Settings, then API Keys.
4 | PASSAGE_API_KEY=
--------------------------------------------------------------------------------
/02-Login-With-Profile/README.md:
--------------------------------------------------------------------------------
1 | # Passage Next.js Example App
2 |
3 | This example application uses the [Passage Auth Element](https://www.npmjs.com/package/@passageidentity/passage-elements) in a Next.js application to authenticate users using biometrics or magic links.
4 |
5 | [Passage Node.js SDK](https://www.npmjs.com/package/@passageidentity/passage-node) is used to verify users on authenticated endpoints. To run this example application, follow the instructions below.
6 |
7 | ## Configure Your Environment Variables
8 |
9 | 1. Copy the text in the `EXAMPLE.env` file to your own `.env` file.
10 | 2. Replace the example variables with your own Passage App ID and API Key. You can get these from the [Passage Console](https://console.passage.id). You'll have to register and login, and then create a new application. (Note that you'll use Passage to do so.)
11 | 3. The App ID is found on the main Dashboard page, and the API Key can be created and retrieved from the `Setting/API Keys` page. **Note that both are required.**
12 |
13 |
14 | ## Getting Started
15 |
16 | First, run the development server:
17 |
18 | ```bash
19 | npm run dev
20 | # or
21 | yarn dev
22 | ```
23 |
24 | Then, ensure that all the dependencies are properly installed by running:
25 |
26 | ```bash
27 | npm install
28 | ```
29 |
30 | Once that is complete, open a browser and navigate to [http://localhost:3000](http://localhost:3000) to see the result.
31 |
32 | 
33 |
34 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
35 |
36 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
37 |
38 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
39 |
40 | # Using Passage with Next.js
41 |
42 | ## Importing and Using the Passage-Auth Custom Element
43 |
44 | The easiest way to add authentication to a web frontend is with a Passage Auth custom element. First you'll need to install the [passage-elements](https://www.npmjs.com/package/@passageidentity/passage-elements) package from npm:
45 |
46 | ```bash
47 | npm i --save @passageidentity/passage-elements
48 | ```
49 | Importing `@passageidentity/passage-elements/passage-auth` triggers a side-effect that will register the passage-auth custom element with the client browser for usage. Since Next.js pre-renders pages on the server this presents a common issue with using web components, such as the Passage elements, in pre-rendered pages - when the server side pre-render occurs there is no client window defined to call `window.customElements.define()` on, which results in an error being thrown.
50 |
51 | The most common solution when using custom elements in pre-rendered applications is to defer the registration of the custom element to a lifecycle hook so that the code is only executed when the client app is executed in browser. This is done in this example in [pages/index.js](https://github.com/passageidentity/example-nextjs/blob/main/pages/index.js):
52 |
53 | ```javascript
54 | export default function Home() {
55 |
56 | useEffect(()=>{
57 | require('@passageidentity/passage-elements/passage-auth');
58 | }, []);
59 |
60 | return (
61 |
62 | ...
63 |
64 | )
65 | }
66 | ```
67 |
68 | Similarly for the dashboard in this example we use the passage-profile element to display user information. The profile element is imported in the same way the auth element is imported:
69 |
70 | ```javascript
71 | function Dashboard({isAuthorized, appID}){
72 |
73 | useEffect(()=>{
74 | require('@passageidentity/passage-elements/passage-profile');
75 | }, []);
76 |
77 | }
78 | ```
79 |
80 | ## Getting Authentication Status and User Information with Server-Side Rendering
81 | After the user has logged in with Passage, all requests need to be authenticated using the JWT provided by Passage. The [Passage Node.js SDK](https://www.npmjs.com/package/@passageidentity/passage-node) to authenticate requests and retrieve user data for your application.
82 |
83 | In this example, we handle authentication securely in Next.js's server-side rendering function [`getServerSideProps()`](https://nextjs.org/docs/basic-features/data-fetching#getserversideprops-server-side-rendering). Per Next.js documention you can import modules in top-level scope for use in `getServerSideProps`. Imports used in `getServerSideProps` will not be bundled for the client-side. This means you can write server-side code directly in `getServerSideProps`.
84 |
85 | The JWT provided by Passage is stored in both cookies and localstorage. Next.js provides the cookies set for an application to `getServerSideProps` which allows passing the JWT from the client browser to the server to handle authentication.
86 |
87 | This is done in this example in pages/dashboard.js.
88 |
--------------------------------------------------------------------------------
/02-Login-With-Profile/components/banner.js:
--------------------------------------------------------------------------------
1 | import styles from '../styles/Banner.module.css';
2 |
3 | export default function Banner() {
4 | return (
5 |
16 | >
17 | );
18 | }
19 |
20 | export default MyApp;
--------------------------------------------------------------------------------
/02-Login-With-Profile/pages/dashboard.js:
--------------------------------------------------------------------------------
1 | // this import is only included in the server build since its only used in getServerSideProps
2 | import Passage from '@passageidentity/passage-node';
3 | import styles from '../styles/App.module.css';
4 | import { useEffect } from 'react';
5 |
6 | function Dashboard({isAuthorized, appID}){
7 |
8 | useEffect(()=>{
9 | require('@passageidentity/passage-elements/passage-profile');
10 | }, []);
11 |
12 | const authorizedBody =
13 | <>
14 | You successfully signed in with Passage.
15 |
16 |
17 | >
18 |
19 | const unauthorizedBody =
20 | <>
21 | You have not logged in and cannot view the dashboard.
22 |
72 | You successfully signed in with Passage.
73 |
74 | Your username is: {userInfo.email}
75 |
76 | Account created at:
77 | {formattedCreatedAt}
78 |