diff --git a/.env.local b/.env.local new file mode 100644 index 0000000..2701d8d --- /dev/null +++ b/.env.local @@ -0,0 +1,16 @@ + +# values generated by Gemini +# Generated for security +AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" + +# Use 'common' for multi-tenant (Work + Personal) support +AUTH_MICROSOFT_ENTRA_ID_TENANT_ID="common" + +# The Application (client) ID +AUTH_MICROSOFT_ENTRA_ID_ID="b549931a-f491-436b-b7bc-d37d8ca3c17e" + +# The Client Secret VALUE (ensure no leading '6' or spaces) +AUTH_MICROSOFT_ENTRA_ID_SECRET="O3p8Q~oMph-0kSLwkvzEJzJdx_iHGOjJjrr5Pa1A" + +# Required to tell Auth.js to trust your localhost/proxy URL +AUTH_TRUST_HOST=true \ No newline at end of file diff --git a/.env.local-bak b/.env.local-bak new file mode 100644 index 0000000..a2e7af6 --- /dev/null +++ b/.env.local-bak @@ -0,0 +1,5 @@ +AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" # Added by `npx auth`. Read more: https://cli.authjs.dev + +AUTH_MICROSOFT_ENTRA_ID="549931a-f491-436b-b7bc-d37d8ca3c17e" +AUTH_MICROSOFT_ENTRA_ID_SECRET="6O3p8Q~oMph-0kSLwkvzEJzJdx_iHGOjJjrr5Pa1A" +AUTH_MICROSOFT_ENTRA_ID_TENANT_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0" \ No newline at end of file diff --git a/.env.local-bak2 b/.env.local-bak2 new file mode 100644 index 0000000..69a3a8f --- /dev/null +++ b/.env.local-bak2 @@ -0,0 +1,5 @@ +AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" # Added by `npx auth`. Read more: https://cli.authjs.dev + +AUTH_MICROSOFT_ENTRA_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0" +AUTH_MICROSOFT_ENTRA_ID_SECRET="6O3p8Q~oMph-0kSLwkvzEJzJdx_iHGOjJjrr5Pa1A" +AUTH_MICROSOFT_ENTRA_ID_TENANT_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0" \ No newline at end of file diff --git a/docs/notes.md b/docs/notes.md index 55fd623..38fd985 100644 --- a/docs/notes.md +++ b/docs/notes.md @@ -1,14 +1,43 @@ -- [1. Project Structure](#1-project-structure) -- [2. App Registrations](#2-app-registrations) - - [2.1. Permissions Needed](#21-permissions-needed) -- [3. Steps](#3-steps) - - [3.1. create nextjs app](#31-create-nextjs-app) - - [3.2. Create .env file](#32-create-env-file) - - [3.3. Configure Git](#33-configure-git) - - [3.4. Test nextjs](#34-test-nextjs) +- [1. Reference](#1-reference) + - [1.1. Next.js Authentication Made Easy with Microsoft Entra ID](#11-nextjs-authentication-made-easy-with-microsoft-entra-id) + - [1.2. Next.js 16 Middleware DEPRECATED - Authentication In Proxy Or Data Access Layer?](#12-nextjs-16-middleware-deprecated---authentication-in-proxy-or-data-access-layer) + - [1.3. Overview of user consent and how to manage it in Microsoft Entra | Microsoft](#13-overview-of-user-consent-and-how-to-manage-it-in-microsoft-entra--microsoft) + - [1.4. Add Tailwind CSS to an Existing Next js Project](#14-add-tailwind-css-to-an-existing-next-js-project) + - [1.5. How to style Material UI component with Tailwindcss in React project](#15-how-to-style-material-ui-component-with-tailwindcss-in-react-project) +- [2. Project Structure](#2-project-structure) +- [3. App Registrations](#3-app-registrations) + - [3.1. Permissions Needed](#31-permissions-needed) +- [4. Steps](#4-steps) + - [4.1. create nextjs app](#41-create-nextjs-app) + - [4.2. Create .env file](#42-create-env-file) + - [4.3. Configure Git](#43-configure-git) + - [4.4. Test nextjs](#44-test-nextjs) + - [4.5. Check Authentication](#45-check-authentication) + - [4.6. Install NextAuth.js v5](#46-install-nextauthjs-v5) +# 1. Reference -# 1. Project Structure +## 1.1. Next.js Authentication Made Easy with Microsoft Entra ID + +[Next.js Authentication Made Easy with Microsoft Entra ID](https://www.youtube.com/watch?v=tNLrMKZ-xmc&t=5s) + +## 1.2. Next.js 16 Middleware DEPRECATED - Authentication In Proxy Or Data Access Layer? + +[Next.js 16 Middleware DEPRECATED - Authentication In Proxy Or Data Access Layer?](https://www.youtube.com/watch?v=zNgCFXZLoRk) + +## 1.3. Overview of user consent and how to manage it in Microsoft Entra | Microsoft + +[Overview of user consent and how to manage it in Microsoft Entra | Microsoft](https://www.youtube.com/watch?v=u6PyQkNi7xk) + +## 1.4. Add Tailwind CSS to an Existing Next js Project + +[Add Tailwind CSS to an Existing Next js Project](https://www.youtube.com/watch?v=vRqTNuX46PY) + +## 1.5. How to style Material UI component with Tailwindcss in React project + +[How to style Material UI component with Tailwindcss in React project](https://www.youtube.com/watch?v=QQIfuMlA6TI) + +# 2. Project Structure The following is the desire structure @@ -34,8 +63,8 @@ web-calibre/ -# 2. App Registrations - +# 3. App Registrations +WebCalibre2 Application (client) ID = 'b549931a-f491-436b-b7bc-d37d8ca3c17e' Object ID ='0b8256a2-e2ac-472b-896a-4b5845bc32fe' @@ -58,12 +87,24 @@ For production: https://your-domain.com/api/auth/callback/azure-ad Note: Replace azure-ad with whatever ID you give the provider in your code. By default, in NextAuth, it is usually azure-ad. -## 2.1. Permissions Needed +WebCalibre3 +|Display name | WebCalibre3 +|------------------------|-------------------------------------| +|Application (client) ID |a95ef644-cb9d-465c-8dae-f822a78a8ac3 | +|Object ID |1e1f2bda-661c-4ad0-9aae-82313c603076 | +|Directory (tenant) ID. |1c06ce7c-7884-4796-8652-d4c32d75a5d0 | + +Client credentials +| Description | Expires | Value | Secret ID +|--------------|---------|-----------------------------------------|------------------------------------| +| WebCalibre3 |1/5/2028 |VZ48Q~EOsgZcgBI25cc0zys.h9zM7hN9I7DhZdnW |d647cf28-80ed-45fb-a89e-e2bc323a40e9| + +## 3.1. Permissions Needed ![Permissions Needed](./images/PermissionsNeeded.png) -# 3. Steps -## 3.1. create nextjs app +# 4. Steps +## 4.1. create nextjs app The following command creates the nextjs folder structure Yes, Next.js has a specific syntax to create a project in your current directory instead of creating a new subfolder. @@ -92,7 +133,7 @@ Configuration: It will then ask you about TypeScript, ESLint, Tailwind CSS, the Based on our previous discussions, I recommend selecting "Yes" for TypeScript, ESLint, Tailwind, the src/ directory, and the App Router to match the professional structure we planned for WebCalibre. -## 3.2. Create .env file +## 4.2. Create .env file @@ -120,6 +161,15 @@ Next.js looks for variables in this specific order (from highest priority to low 4. .env.development 5. .env (The final fallback) +```text +DATABASE_URL="postgresql://stephen:Web2025$$@192.168.1.210:5432/webcalibre2" + +# Credentials for your Microsoft App (NextAuth) +AZURE_AD_CLIENT_ID="549931a-f491-436b-b7bc-d37d8ca3c17e" +AZURE_AD_CLIENT_SECRET="6a242be1-c711-4cc3-a132-03c1f57993ed" +AZURE_AD_TENANT_ID="1c06ce7c-7884-4796-8652-d4c32d75a5d0" +``` + **Summary Comparison Table** |Feature |.env |.env.local| @@ -131,7 +181,7 @@ Next.js looks for variables in this specific order (from highest priority to low **Best Practice Tip: ** Since .env.local isn't shared on GitHub, it’s a good idea to create a file named .env.example in your project. This file should contain the names of the keys (e.g., AZURE_AD_CLIENT_SECRET=) but leave the values blank, so other developers know which variables they need to create on their own machines. -## 3.3. Configure Git +## 4.3. Configure Git Nextjs initializes git by default. But is doesn't add some configs that I do @@ -143,7 +193,7 @@ git remote -v origin /Users/stephenlohning/Library/CloudStorage/OneDrive-Personal/Documents/10_GIT_Repositories/124_WebCalibre2.git (fetch) origin /Users/stephenlohning/Library/CloudStorage/OneDrive-Personal/Documents/10_GIT_Repositories/124_WebCalibre2.git (push) -## 3.4. Test nextjs +## 4.4. Test nextjs ```zsh npm run dev @@ -158,4 +208,25 @@ npm run dev ✓ Starting... ✓ Ready in 648ms ``` -If it working proceed \ No newline at end of file +If it working proceed + +## 4.5. Check Authentication + +I ran through the Video:- + +[Next.js Authentication Made Easy with Microsoft Entra ID](https://www.youtube.com/watch?v=tNLrMKZ-xmc&t=5s) + +## 4.6. Install NextAuth.js v5 + +npm install next-auth@beta + +Setup Environment +The only environment variable that is mandatory is the AUTH_SECRET. This is a random value used by the library to encrypt tokens and email verification hashes. (See Deployment to learn more). You can generate one via the official Auth.js CLI running: + +npx auth secret + +created .env.local + +```text +AUTH_SECRET="7cz3Z4kUI2kB3mdAPo58iioUDLSRJ92X8+boEixvO8k=" # Added by `npx auth`. Read more: https://cli.authjs.dev +``` \ No newline at end of file diff --git a/docs/notes.pdf b/docs/notes.pdf index 6dd0efe..fbb81ea 100644 Binary files a/docs/notes.pdf and b/docs/notes.pdf differ diff --git a/package-lock.json b/package-lock.json index 08359c0..58c8792 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "next": "16.1.1", + "next-auth": "^5.0.0-beta.30", "react": "19.2.3", "react-dom": "19.2.3" }, @@ -21,6 +22,35 @@ "typescript": "^5" } }, + "node_modules/@auth/core": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.41.0.tgz", + "integrity": "sha512-Wd7mHPQ/8zy6Qj7f4T46vg3aoor8fskJm6g2Zyj064oQ3+p0xNZXAV60ww0hY+MbTesfu29kK14Zk5d5JTazXQ==", + "license": "ISC", + "dependencies": { + "@panva/hkdf": "^1.2.1", + "jose": "^6.0.6", + "oauth4webapi": "^3.3.0", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -1211,6 +1241,15 @@ "node": ">=12.4.0" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -4082,6 +4121,15 @@ "node": ">= 0.4" } }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4413,6 +4461,33 @@ } } }, + "node_modules/next-auth": { + "version": "5.0.0-beta.30", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.30.tgz", + "integrity": "sha512-+c51gquM3F6nMVmoAusRJ7RIoY0K4Ts9HCCwyy/BRoe4mp3msZpOzYMyb5LAYc1wSo74PMQkGDcaghIO7W6Xjg==", + "license": "ISC", + "dependencies": { + "@auth/core": "0.41.0" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "next": "^14.0.0-0 || ^15.0.0 || ^16.0.0", + "nodemailer": "^7.0.7", + "react": "^18.2.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", @@ -4420,6 +4495,15 @@ "dev": true, "license": "MIT" }, + "node_modules/oauth4webapi": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.8.3.tgz", + "integrity": "sha512-pQ5BsX3QRTgnt5HxgHwgunIRaDXBdkT23tf8dfzmtTIL2LTpdmxgbpbBm0VgFWAIDlezQvQCTgnVIUmHupXHxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4708,6 +4792,25 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.24.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "license": "MIT", + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index 6f4d308..0813faa 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "next": "16.1.1", + "next-auth": "^5.0.0-beta.30", "react": "19.2.3", "react-dom": "19.2.3" }, diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..0cf1408 --- /dev/null +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,2 @@ +import { handlers } from "@/auth" // Referring to the auth.ts we just created +export const { GET, POST } = handlers \ No newline at end of file diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..adde291 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +function Dashboard() { + return ( +
Dashboard
+ ) +} + +export default Dashboard \ No newline at end of file diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000..a71c124 --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,20 @@ +import LoginButton from "@/components/LoginButton"; + +function Login(){ + return ( +
+
+

Login

+

+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus + reprehenderit aut vitae consequuntur deleniti itaque totam eaque + perspiciatis nulla magnam officiis blanditiis enim fuga perferendis + deserunt, praesentium vel. Omnis, dolorum? +

+ +
+
+ ); +} + +export default Login \ No newline at end of file diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx new file mode 100644 index 0000000..14d73ef --- /dev/null +++ b/src/app/profile/page.tsx @@ -0,0 +1,12 @@ + + +function Profile +() { + return ( +
+ Profile +
+ ) +} + +export default Profile diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..1bf98f9 --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,21 @@ +import NextAuth from "next-auth" +import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id" + +export const { handlers, signIn, signOut, auth } = NextAuth({ + providers: [ + MicrosoftEntraID({ + clientId: process.env.AUTH_MICROSOFT_ENTRA_ID_ID, + clientSecret: process.env.AUTH_MICROSOFT_ENTRA_ID_SECRET, + // Pass tenantId as 'common' to support multi-tenant + personal accounts + tenantId: process.env.AUTH_MICROSOFT_ENTRA_ID_TENANT_ID, + authorization: { + params: { + // Explicitly ask for these scopes for OneDrive access later + scope: "openid profile email offline_access User.Read Files.ReadWrite", + }, + }, + }) + ], + // Required for Next.js 15/16 and Nginx production environments + trustHost: true, +}) \ No newline at end of file diff --git a/src/components/LoginButton.tsx b/src/components/LoginButton.tsx new file mode 100644 index 0000000..1357303 --- /dev/null +++ b/src/components/LoginButton.tsx @@ -0,0 +1,24 @@ +"use client"; +import { signIn } from "next-auth/react"; +import { useState } from "react"; + +function LoginButton() { + const [isLoading, setIsLoading] = useState(false); + + const handleLogin = async () => { + setIsLoading(true); + await signIn("microsoft-entra-id"); + setIsLoading(false); + }; + return ( + + ); +} + +export default LoginButton; \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..a5f11e0 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,40 @@ +import { NextResponse } from "next/server"; +import {auth } from "@/auth"; +//export { auth as middleware } from "@/auth"; + + + +const protectedRoutes=["/dashboard","/profile"] +const authPageRoutes=["/login"] +const apiAuthPrefix ="/api/auth" + +export default auth( (req) =>{ + const { nextUrl } = req; + //const authData = await req.auth; + const isLoggedIn = !!req.auth; + + const path = nextUrl.pathname + const isApiAuthRoute = nextUrl.pathname.startsWith(apiAuthPrefix); + const isProtectedRoute = protectedRoutes.includes(path); + const isAuthPageRoute = authPageRoutes.includes(path); + + // console.log({ authData }); + + if(isApiAuthRoute){ + return NextResponse.next(); + } + + if(isProtectedRoute && !isLoggedIn){ + return NextResponse.redirect(new URL("/login", req.nextUrl)); + } + + if(isLoggedIn && isAuthPageRoute){ + return NextResponse.redirect(new URL("/dashboard",req.nextUrl)) + } + + return NextResponse.next(); +}) + +export const config = { + matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], +} \ No newline at end of file