├── 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 | ![Passage Login Screen](public/passageloginscreen.png) 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 |
6 |
7 |
Passage + Next.js Example App
8 |
9 | Go to Passage 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /01-Login/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /01-Login/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passage-next-app", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "@passageidentity/passage-elements": "latest", 12 | "@passageidentity/passage-node": "latest", 13 | "next": "12.0.10", 14 | "react": "17.0.2", 15 | "react-dom": "17.0.2" 16 | }, 17 | "devDependencies": { 18 | "eslint": "8.8.0", 19 | "eslint-config-next": "12.0.10" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /01-Login/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | 3 | import Banner from '../components/banner'; 4 | import styles from '../styles/App.module.css'; 5 | 6 | function MyApp({ Component, pageProps }) { 7 | return ( 8 | <> 9 | 10 |
11 | 12 |
13 |
14 | Learn more with our Documentation and Github. 15 |
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 |

18 | Login to continue. 19 | 20 | 21 | return ( 22 |
23 |
{isAuthorized ? 'Welcome!' : 'Unauthorized'}
24 |
25 | { isAuthorized ? authorizedBody : unauthorizedBody } 26 |
27 |
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 | ![Passage Login Screen](public/passageloginscreen.png) 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 |
6 |
7 |
Passage + Next.js Example App
8 |
9 | Go to Passage 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /02-Login-With-Profile/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | } 4 | -------------------------------------------------------------------------------- /02-Login-With-Profile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "passage-next-app", 3 | "private": true, 4 | "scripts": { 5 | "dev": "next dev", 6 | "build": "next build", 7 | "start": "next start", 8 | "lint": "next lint" 9 | }, 10 | "dependencies": { 11 | "@passageidentity/passage-elements": "latest", 12 | "@passageidentity/passage-node": "latest", 13 | "next": "12.0.10", 14 | "react": "17.0.2", 15 | "react-dom": "17.0.2" 16 | }, 17 | "devDependencies": { 18 | "eslint": "8.8.0", 19 | "eslint-config-next": "12.0.10" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /02-Login-With-Profile/pages/_app.js: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css'; 2 | 3 | import Banner from '../components/banner'; 4 | import styles from '../styles/App.module.css'; 5 | 6 | function MyApp({ Component, pageProps }) { 7 | return ( 8 | <> 9 | 10 |
11 | 12 |
13 |
14 | Learn more with our Documentation and Github. 15 |
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 |

23 | Login to continue. 24 | 25 | 26 | return ( 27 |
28 |
{isAuthorized ? 'Welcome!' : 'Unauthorized'}
29 |
30 | { isAuthorized ? authorizedBody : unauthorizedBody } 31 |
32 |
33 | ); 34 | }; 35 | 36 | export async function getServerSideProps(context) { 37 | // getServerSideProps runs server-side only and will never execute on the client browser 38 | // this allows the safe use of a private Passage API Key 39 | const appID = process.env.PASSAGE_APP_ID; 40 | const passage = new Passage({ 41 | appID, 42 | apiKey: process.env.PASSAGE_API_KEY, 43 | authStrategy: "HEADER", 44 | }); 45 | try { 46 | const authToken = context.req.cookies['psg_auth_token']; 47 | const req = { 48 | headers: { 49 | authorization: `Bearer ${authToken}`, 50 | }, 51 | }; 52 | const userID = await passage.authenticateRequest(req); 53 | if (userID) { 54 | return { props: {isAuthorized: true, appID: appID} }; 55 | } 56 | } catch (error) { 57 | // authentication failed 58 | return { props: {isAuthorized: false, appID: appID} }; 59 | } 60 | } 61 | 62 | export default Dashboard; -------------------------------------------------------------------------------- /02-Login-With-Profile/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 | -------------------------------------------------------------------------------- /02-Login-With-Profile/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passageidentity/example-nextjs/722949a74370f5d0842b67b34b21ecfcb0b32fa9/02-Login-With-Profile/public/favicon.ico -------------------------------------------------------------------------------- /02-Login-With-Profile/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: 600px; 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 | -------------------------------------------------------------------------------- /02-Login-With-Profile/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 | } -------------------------------------------------------------------------------- /02-Login-With-Profile/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 | -------------------------------------------------------------------------------- /03-Login-app-directory/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_PASSAGE_APP_ID= 2 | -------------------------------------------------------------------------------- /03-Login-app-directory/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /03-Login-app-directory/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /03-Login-app-directory/README.md: -------------------------------------------------------------------------------- 1 | # Passage + Next.js 13.4 App Router Example 2 | 3 | 4 | ## Getting Started 5 | 6 | ### Either fork the repo or directly clone it 7 | 8 | ### To directly clone the repo 9 | 10 | ```shell 11 | git clone https://github.com/passageidentity/example-nextjs.git 12 | ``` 13 | Then 14 | 15 | ```shell 16 | cd 03-Login-app-directory 17 | ``` 18 | 19 | ### Install packages 20 | 21 | ```shell 22 | npm i 23 | ``` 24 | 25 | ### Setup .env file 26 | 27 | ```js 28 | NEXT_PUBLIC_PASSAGE_APP_ID= 29 | ``` 30 | 31 | ### Start the app 32 | 33 | ```shell 34 | npm run dev 35 | ``` 36 | 37 | ## Available commands 38 | 39 | Running commands with npm `npm run [command]` 40 | 41 | | command | description | 42 | | :-------------- | :--------------------------------------- | 43 | | `dev` | Starts a development instance of the app | 44 | | `build` | To build your application | 45 | | `start` | Starts a production instance of the app | 46 | -------------------------------------------------------------------------------- /03-Login-app-directory/actions/getCurrentUserInfo.ts: -------------------------------------------------------------------------------- 1 | import { Passage } from "@passageidentity/passage-js"; 2 | 3 | export interface PassageUserInfo { 4 | email: string; 5 | created_at: string; 6 | } 7 | 8 | export async function getCurrentUserInfo() { 9 | const passage = new Passage(process.env.NEXT_PUBLIC_PASSAGE_APP_ID!); 10 | try { 11 | const user = passage.getCurrentUser(); 12 | const userInfo = await user.userInfo(); 13 | return { 14 | userInfo, 15 | }; 16 | } catch (error) { 17 | console.log(error); 18 | } 19 | return { userInfo: undefined }; 20 | } 21 | -------------------------------------------------------------------------------- /03-Login-app-directory/app/about/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { FC } from "react"; 3 | 4 | interface pageProps {} 5 | 6 | const page: FC = ({}) => { 7 | return ( 8 | <> 9 |
10 |

11 | This is a route protected by the middleware. Should not be visible if 12 | the user is not logged in 13 |

14 |
15 | Back to Home 16 | 17 | Back to Dashboard 18 | 19 | 20 | Blog (protected route) 21 | 22 |
23 |
24 | 25 | ); 26 | }; 27 | 28 | export default page; 29 | -------------------------------------------------------------------------------- /03-Login-app-directory/app/blog/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { FC } from "react"; 3 | 4 | interface pageProps {} 5 | 6 | const page: FC = ({}) => { 7 | return ( 8 | <> 9 |
10 |

Blog Slug page

11 |

12 | This is a route protected by the middleware. Should not be visible if 13 | the user is not logged in 14 |

15 |
16 | Back to Home 17 | 18 | Back to Dashboard 19 | 20 | 21 | Back to Blog Home (protected) 22 | 23 | 24 | About (protected route) 25 | 26 | 27 |
28 |
29 | 30 | ); 31 | }; 32 | 33 | export default page; 34 | -------------------------------------------------------------------------------- /03-Login-app-directory/app/blog/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { FC } from "react"; 3 | 4 | interface pageProps {} 5 | 6 | const page: FC = ({}) => { 7 | return ( 8 | <> 9 |
10 |

11 | This is a route protected by the middleware. Should not be visible if 12 | the user is not logged in 13 |

14 |
15 | Back to Home 16 | 17 | Back to Dashboard 18 | 19 | 20 | About (protected route) 21 | 22 | 23 | Blog Slog Page (protected route) 24 | 25 |
26 |
27 | 28 | ); 29 | }; 30 | 31 | export default page; 32 | -------------------------------------------------------------------------------- /03-Login-app-directory/app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react"; 2 | import DashboardContent from "@/components/DashboardContent"; 3 | import Link from "next/link"; 4 | 5 | interface pageProps {} 6 | 7 | const page: FC = ({}) => { 8 | return ( 9 | <> 10 | 11 |
12 | Back to Home 13 | 14 | Blog(protected route) 15 | 16 |
17 | 18 | ); 19 | }; 20 | 21 | export default page; 22 | -------------------------------------------------------------------------------- /03-Login-app-directory/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passageidentity/example-nextjs/722949a74370f5d0842b67b34b21ecfcb0b32fa9/03-Login-app-directory/app/favicon.ico -------------------------------------------------------------------------------- /03-Login-app-directory/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | -------------------------------------------------------------------------------- /03-Login-app-directory/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Banner from "@/components/banner"; 2 | import "./globals.css"; 3 | import { Inter } from "next/font/google"; 4 | import Footer from "@/components/footer"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export const metadata = { 9 | title: "Passage + Next.js Example With /app directory", 10 | description: 11 | "This is an example of how you can integrate passage with the Next.Js /app directory to Register, Login and also get Authentication Status and User Information.", 12 | }; 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 21 | 22 | 23 | {children} 24 |