part working authentication

This commit is contained in:
stephen 2026-01-06 13:40:21 +11:00
parent 9359b8f306
commit 8d62b782f7
14 changed files with 347 additions and 18 deletions

16
.env.local Normal file
View file

@ -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

5
.env.local-bak Normal file
View file

@ -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"

5
.env.local-bak2 Normal file
View file

@ -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"

View file

@ -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, its 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
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
```

Binary file not shown.

103
package-lock.json generated
View file

@ -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",

View file

@ -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"
},

View file

@ -0,0 +1,2 @@
import { handlers } from "@/auth" // Referring to the auth.ts we just created
export const { GET, POST } = handlers

View file

@ -0,0 +1,9 @@
import React from 'react'
function Dashboard() {
return (
<div>Dashboard</div>
)
}
export default Dashboard

20
src/app/login/page.tsx Normal file
View file

@ -0,0 +1,20 @@
import LoginButton from "@/components/LoginButton";
function Login(){
return (
<div className="flex items-center justify-center min-h-[90vh]">
<div className="p-5 border rounded-md max-w-[50%] flex gap-5 flex-col">
<h1 className="text-center font-semibold text-xl">Login</h1>
<p>
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?
</p>
<LoginButton />
</div>
</div>
);
}
export default Login

12
src/app/profile/page.tsx Normal file
View file

@ -0,0 +1,12 @@
function Profile
() {
return (
<div>
Profile
</div>
)
}
export default Profile

21
src/auth.ts Normal file
View file

@ -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,
})

View file

@ -0,0 +1,24 @@
"use client";
import { signIn } from "next-auth/react";
import { useState } from "react";
function LoginButton() {
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleLogin = async () => {
setIsLoading(true);
await signIn("microsoft-entra-id");
setIsLoading(false);
};
return (
<button
onClick={() => handleLogin()}
className="w-full bg-blue-800 text-white my-5 p-3 rounded-md hover:opacity-80 disabled:opacity-75"
disabled={isLoading}
>
{isLoading ? "Signing in..." : "Login With Microsoft"}
</button>
);
}
export default LoginButton;

40
src/middleware.ts Normal file
View file

@ -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).*)"],
}