├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── api │ ├── auth │ │ ├── login │ │ │ └── route.js │ │ └── logout │ │ │ └── route.js │ ├── hello │ │ └── route.js │ └── profile │ │ └── route.js ├── dashboard │ └── page.jsx ├── globals.css ├── layout.jsx ├── login │ └── page.jsx └── page.jsx ├── middleware.js ├── next.config.js ├── package-lock.json ├── package.json └── public ├── favicon.ico └── vercel.svg /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. 16 | 17 | [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`. 18 | 19 | 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. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /app/api/auth/login/route.js: -------------------------------------------------------------------------------- 1 | import { sign } from "jsonwebtoken"; 2 | import { NextResponse } from "next/server"; 3 | 4 | export async function POST(request) { 5 | const { email, password } = await request.json(); 6 | 7 | if (email === "admin@local.local" && password === "admin") { 8 | // expire in 30 days 9 | const token = sign( 10 | { 11 | exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 30, 12 | email, 13 | username: "fazt", 14 | }, 15 | "secret" 16 | ); 17 | 18 | const response = NextResponse.json({ 19 | token, 20 | }); 21 | 22 | response.cookies.set({ 23 | name: "myTokenName", 24 | value: token, 25 | httpOnly: true, 26 | secure: process.env.NODE_ENV === "production", 27 | sameSite: "strict", 28 | maxAge: 1000 * 60 * 60 * 24 * 30, 29 | path: "/", 30 | }); 31 | 32 | return response; 33 | } else { 34 | return NextResponse.json( 35 | { 36 | message: "Invalid credentials", 37 | }, 38 | { 39 | status: 401, 40 | } 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/api/auth/logout/route.js: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { cookies } from "next/headers"; 3 | 4 | export function GET(request) { 5 | const cookieStore = cookies(); 6 | const token = cookieStore.get("myTokenName"); 7 | console.log(token); 8 | 9 | if (!token) { 10 | return NextResponse.json({ 11 | message: "Not logged in", 12 | }, { 13 | status: 401, 14 | }) 15 | } 16 | 17 | try { 18 | cookieStore.delete("myTokenName"); 19 | 20 | const response = NextResponse.json( 21 | {}, 22 | { 23 | status: 200, 24 | } 25 | ); 26 | 27 | return response; 28 | } catch (error) { 29 | console.log(error); 30 | return NextResponse.json(error.message, { 31 | status: 500, 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/api/hello/route.js: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | 3 | export function GET(request) { 4 | return NextResponse.json({ 5 | message: "Hello World", 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /app/api/profile/route.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import { cookies } from "next/headers"; 3 | import { NextResponse } from "next/server"; 4 | 5 | export function GET(request) { 6 | const cookieStore = cookies(); 7 | const token = cookieStore.get("myTokenName"); 8 | 9 | if (!token) { 10 | return res.status(401).json({ error: "Not logged in" }); 11 | } 12 | 13 | const {email, username} = jwt.verify(token.value, "secret"); 14 | 15 | return NextResponse.json({ 16 | email, 17 | username, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /app/dashboard/page.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import { useState } from "react"; 5 | import { useRouter } from "next/navigation"; 6 | 7 | function Dashboard() { 8 | const [user, setUser] = useState({ 9 | email: "", 10 | username: "", 11 | }); 12 | const router = useRouter(); 13 | 14 | const getProfile = async () => { 15 | const profile = await axios.get("/api/profile"); 16 | setUser(profile.data); 17 | }; 18 | 19 | const logout = async () => { 20 | try { 21 | const res = await axios.get("/api/auth/logout"); 22 | console.log(res); 23 | } catch (error) { 24 | console.error(error.message); 25 | } 26 | router.push("/login"); 27 | }; 28 | return ( 29 |
30 | {JSON.stringify(user)} 31 | 32 | 33 |
34 | ); 35 | } 36 | 37 | export default Dashboard; 38 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #202020; 3 | color: white; 4 | } 5 | -------------------------------------------------------------------------------- /app/layout.jsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | 3 | function Layout({ children }) { 4 | return ( 5 | 6 | {children} 7 | 8 | ); 9 | } 10 | 11 | export default Layout; 12 | -------------------------------------------------------------------------------- /app/login/page.jsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import axios from "axios"; 4 | import { useState } from "react"; 5 | import { useRouter } from "next/navigation"; 6 | 7 | function Home() { 8 | const [credentials, setCredentials] = useState({ 9 | email: "", 10 | password: "", 11 | }); 12 | const router = useRouter(); 13 | 14 | const handleSubmit = async (e) => { 15 | e.preventDefault(); 16 | const res = await axios.post("/api/auth/login", credentials); 17 | 18 | if (res.status === 200) { 19 | router.push("/dashboard"); 20 | } 21 | }; 22 | 23 | return ( 24 |
25 |
26 | 30 | setCredentials({ 31 | ...credentials, 32 | email: e.target.value, 33 | }) 34 | } 35 | /> 36 | 40 | setCredentials({ 41 | ...credentials, 42 | password: e.target.value, 43 | }) 44 | } 45 | /> 46 | 47 |
48 |
49 | ); 50 | } 51 | 52 | export default Home; 53 | -------------------------------------------------------------------------------- /app/page.jsx: -------------------------------------------------------------------------------- 1 | function HomePage() { 2 | return ( 3 |

Home Page

4 | ) 5 | } 6 | 7 | export default HomePage; 8 | -------------------------------------------------------------------------------- /middleware.js: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import { jwtVerify } from "jose"; 3 | 4 | export async function middleware(request) { 5 | const jwt = request.cookies.get("myTokenName"); 6 | 7 | if (!jwt) return NextResponse.redirect(new URL("/login", request.url)); 8 | 9 | // this condition avoid to show the login page if the user is logged in 10 | // if (jwt) { 11 | // if (request.nextUrl.pathname.includes("/login")) { 12 | // try { 13 | // await jwtVerify(jwt, new TextEncoder().encode("secret")); 14 | // return NextResponse.redirect(new URL("/dashboard", request.url)); 15 | // } catch (error) { 16 | // return NextResponse.next(); 17 | // } 18 | // } 19 | // } 20 | 21 | try { 22 | const { payload } = await jwtVerify( 23 | jwt.value, 24 | new TextEncoder().encode("secret") 25 | ); 26 | return NextResponse.next(); 27 | } catch (error) { 28 | return NextResponse.redirect(new URL("/login", request.url)); 29 | } 30 | } 31 | 32 | export const config = { 33 | matcher: ["/dashboard/:path*"], 34 | }; 35 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | } 6 | 7 | module.exports = nextConfig 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-jwt-cookie", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "axios": "^1.4.0", 13 | "cookie": "^0.5.0", 14 | "jose": "^4.14.4", 15 | "jsonwebtoken": "^9.0.1", 16 | "next": "13.4.12", 17 | "react": "18.2.0", 18 | "react-dom": "18.2.0" 19 | }, 20 | "devDependencies": { 21 | "eslint": "8.46.0", 22 | "eslint-config-next": "13.4.12" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FaztWeb/nextjs-jwt-cookie/1030d66004b54bbc4b0f7f3d5a2b934017480616/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | --------------------------------------------------------------------------------