├── .npmrc ├── app.vue ├── public └── favicon.ico ├── tsconfig.json ├── .gitignore ├── composables ├── useAuthUser.ts └── useAuth.ts ├── server ├── api │ └── user │ │ ├── logout.get.ts │ │ ├── users.get.ts │ │ ├── token.get.ts │ │ └── index.post.ts ├── utils │ ├── password.ts │ └── session.ts ├── middleware │ └── user.ts ├── services │ └── user.ts └── models │ └── user.ts ├── plugins └── auth.ts ├── middleware ├── user-only.ts ├── admin-only.ts └── guest-only.ts ├── types.d.ts ├── pages ├── login.vue ├── index.vue ├── public.vue ├── private.vue └── admin.vue ├── nuxt.config.ts ├── components ├── LoginCredentials.vue └── LoginForm.vue ├── package.json ├── README.md └── layouts └── default.vue /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PepeGonzale/nuxt3-auth-jwt-example/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log* 3 | .nuxt 4 | .nitro 5 | .cache 6 | .output 7 | .env 8 | dist 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /composables/useAuthUser.ts: -------------------------------------------------------------------------------- 1 | export const useAuthUser = () => { 2 | return useState('user', () => null) 3 | } 4 | -------------------------------------------------------------------------------- /server/api/user/logout.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler((event) => { 2 | deleteCookie(event, '__session') 3 | 4 | return { 5 | user: null 6 | } 7 | }) -------------------------------------------------------------------------------- /plugins/auth.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtPlugin(async () => { 2 | const { userLoggedIn } = await useAuth() 3 | const user = useAuthUser() 4 | await userLoggedIn() 5 | 6 | }) 7 | -------------------------------------------------------------------------------- /middleware/user-only.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware(async () => { 2 | const authUser = useAuthUser() 3 | 4 | if (!authUser.value) { 5 | return navigateTo({ name: "login" }) 6 | } 7 | }) 8 | -------------------------------------------------------------------------------- /middleware/admin-only.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware(async (_to, from) => { 2 | const { userAdmin } = useAuth() 3 | if (!userAdmin.value) { 4 | return navigateTo({name: "login"}) 5 | } 6 | }) 7 | -------------------------------------------------------------------------------- /server/api/user/users.get.ts: -------------------------------------------------------------------------------- 1 | import { getUsers } from "~/server/models/user" 2 | 3 | export default defineEventHandler(async (event) => { 4 | const user = await getUsers() 5 | return { 6 | users: user 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /middleware/guest-only.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtRouteMiddleware(async (_to, _from) => { 2 | const user = useAuthUser() 3 | 4 | if (user.value) { 5 | if (process.server) 6 | return navigateTo({ name: 'index' }) 7 | 8 | return abortNavigation() 9 | } 10 | }) -------------------------------------------------------------------------------- /server/utils/password.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs" 2 | 3 | export const verifyPassword = async (password: string, hash: string) => { 4 | const compare = await bcrypt.compare(password, hash) 5 | if (!compare) { 6 | throw new Error('Password does not match') 7 | } 8 | return compare 9 | } 10 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | interface User { 2 | email: string; 3 | id: string; 4 | password:string; 5 | role:string[] 6 | } 7 | 8 | interface UserInput { 9 | email :string 10 | password:string 11 | } 12 | 13 | interface UserWithoutPassword { 14 | email: string; 15 | id: string; 16 | role:string[] 17 | 18 | } -------------------------------------------------------------------------------- /pages/login.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Login Page 4 | This page can be accesed by guests 5 | 6 | 7 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | runtimeConfig: { 4 | // Will be available in both server and client 5 | tokenSecret: process.env.TOKEN_SECRET, 6 | tokenExpiration: process.env.TOKEN_EXPIRES, 7 | tokenName: process.env.TOKEN_NAME 8 | }, 9 | modules: ['@nuxtjs/tailwindcss'] 10 | }) 11 | -------------------------------------------------------------------------------- /server/middleware/user.ts: -------------------------------------------------------------------------------- 1 | import { getUserToken } from "../utils/session" 2 | 3 | export default defineEventHandler(async (event) => { 4 | // ... 5 | const user = await getUserToken(event) 6 | console.log(user); 7 | 8 | if (!user) { 9 | event.context.user = null 10 | } 11 | if (user) { 12 | event.context.user = user 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /server/api/user/token.get.ts: -------------------------------------------------------------------------------- 1 | export default defineEventHandler((event) => { 2 | const userWithPassword = event.context.user 3 | if (!userWithPassword) { 4 | return { 5 | user: null 6 | } 7 | } 8 | const { password: _password, ...userWithoutPassword } = userWithPassword 9 | return { 10 | user: userWithoutPassword 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Home 5 | 6 | Simple authentication with Nuxt3 7 | current user: {{ authUser.email }} 8 | 9 | 10 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /server/api/user/index.post.ts: -------------------------------------------------------------------------------- 1 | import { loginUser } from "~/server/services/user" 2 | 3 | export default defineEventHandler(async (event) => { 4 | const body = await readBody(event) 5 | const user = await loginUser(body) 6 | const token = await createToken(user) 7 | const isAdmin = user.role.includes('admin') 8 | setCookie(event, "__session", token) 9 | return { 10 | user, 11 | token, 12 | isAdmin 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /pages/public.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Public 5 | 6 | This page can be accessed by anyone current user: 7 | {{ authUser.email }} 8 | null 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /pages/private.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | Private 13 | 14 | This page is only visible for user who logged in current user: 15 | {{ authUser }} 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /server/services/user.ts: -------------------------------------------------------------------------------- 1 | import { getUserByEmail } from "../models/user" 2 | import { verifyPassword } from "../utils/password" 3 | 4 | const loginUser = async (user: User) => { 5 | const checkUser = getUserByEmail(user.email) 6 | if (!checkUser) { 7 | throw new Error("User not found") 8 | } 9 | const checkPassword = await verifyPassword(user.password, checkUser.password) 10 | if (!checkPassword) { 11 | throw new Error("Password is incorrect") 12 | } 13 | 14 | return checkUser 15 | } 16 | 17 | export { loginUser } 18 | -------------------------------------------------------------------------------- /components/LoginCredentials.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Try this user credentials 5 | 6 | email: user@gmail.com 7 | password: password 8 | 9 | 10 | 11 | Try this admin credentials 12 | 13 | email: admin@gmail.com 14 | password: password 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pages/admin.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Admin Page 4 | This page should only be visible if user is logged in and has admin role 5 | 6 | 7 | 8 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-app", 3 | "private": true, 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare" 10 | }, 11 | "devDependencies": { 12 | "@nuxtjs/tailwindcss": "^6.6.7", 13 | "@types/node": "^18", 14 | "netlify-cli": "^14.3.1", 15 | "nuxt": "^3.4.2" 16 | }, 17 | "dependencies": { 18 | "@nuxtjs/eslint-config-typescript": "^12.0.0", 19 | "@types/bcryptjs": "^2.4.2", 20 | "@types/jsonwebtoken": "^9.0.2", 21 | "bcryptjs": "^2.4.3", 22 | "install": "^0.13.0", 23 | "jsonwebtoken": "^9.0.0", 24 | "npm": "^9.6.5" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/models/user.ts: -------------------------------------------------------------------------------- 1 | const users: User[] = [ 2 | { 3 | email: "admin@gmail.com", 4 | id: "8979e0f2-88e5-4995-9202-3e4035828d95", 5 | password: "$2a$10$q9RCHgoDplabHe/iq5HkduFSe6/o3DLjmkW6b8j6AyLpPIvwqRpeq", 6 | role: ["admin"] 7 | }, 8 | { 9 | email: "user@gmail.com", 10 | id: "bc12597f-1dc1-474c-b431-00ae88a67c25", 11 | password: "$2a$10$3zU3itd/AiI8XBtx8W.HreBBtJdUlGP0GE0Os3/hbceXjYjmtj6HG", 12 | role: ["user"] 13 | } 14 | ] 15 | 16 | const getUsers = (): User[] => { 17 | return users 18 | } 19 | const getUserById = (id: string) => { 20 | return users.find(u => u.id === id) 21 | } 22 | const getUserByEmail = (email: string) => { 23 | return users.find(u => u.email === email) 24 | } 25 | export { getUsers, getUserById, getUserByEmail } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt 3 Minimal Starter 2 | 3 | Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. 4 | 5 | ## Setup 6 | 7 | Make sure to install the dependencies: 8 | 9 | ```bash 10 | # yarn 11 | yarn install 12 | 13 | # npm 14 | npm install 15 | 16 | # pnpm 17 | pnpm install 18 | ``` 19 | 20 | ## Development Server 21 | 22 | Start the development server on `http://localhost:3000` 23 | 24 | ```bash 25 | npm run dev 26 | ``` 27 | 28 | ## Production 29 | 30 | Build the application for production: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | Locally preview production build: 37 | 38 | ```bash 39 | npm run preview 40 | ``` 41 | 42 | Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. 43 | -------------------------------------------------------------------------------- /server/utils/session.ts: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken" 2 | const createToken = async (user: User) => { 3 | const config = useRuntimeConfig() 4 | return await jwt.sign( 5 | { 6 | id: user.id, 7 | email: user.email 8 | }, 9 | config.tokenSecret, 10 | 11 | { 12 | expiresIn: config.tokenExpiration 13 | } 14 | ) 15 | } 16 | const verifyToken = async (token: string) => { 17 | try { 18 | const config = useRuntimeConfig() 19 | return await jwt.verify(token, config.tokenSecret) 20 | } catch (err) { 21 | return "Token expired" 22 | } 23 | } 24 | 25 | const getUserToken = (event) => { 26 | const cookie = getCookie(event, "__session") 27 | if (!cookie) { 28 | return null 29 | } 30 | const token = verifyToken(cookie) 31 | if (!token) { 32 | return null 33 | } 34 | return token 35 | } 36 | export { createToken, getUserToken } 37 | -------------------------------------------------------------------------------- /components/LoginForm.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 19 | Login 20 | 21 | 22 | 23 | 39 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Home 6 | Public 7 | Private 8 | Admin 9 | 13 | Login 14 | Logout 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /composables/useAuth.ts: -------------------------------------------------------------------------------- 1 | export const useAuth = () => { 2 | const authUser = useAuthUser() 3 | const userAdmin = useState('userAdmin', () => false) 4 | const setUser = (user: User) => { 5 | authUser.value = user 6 | } 7 | const login = async (user: UserInput) => { 8 | try { 9 | const data = await $fetch('/api/user', { 10 | method: 'POST', 11 | body: user 12 | }) 13 | console.log(data) 14 | if (data.isAdmin) { 15 | userAdmin.value = true 16 | } else { 17 | userAdmin.value = false 18 | } 19 | setUser(data.user) 20 | return data 21 | } catch (err) { 22 | return null 23 | } 24 | } 25 | const userLoggedIn = async () => { 26 | if (!authUser.value) { 27 | const data = await $fetch('/api/user/token', { 28 | headers: useRequestHeaders(['cookie']) 29 | }) 30 | setUser(data.user) 31 | return data 32 | } 33 | } 34 | const logout = async () => { 35 | const data = await $fetch('/api/user/logout') 36 | userAdmin.value = false 37 | setUser(data.user) 38 | return data 39 | } 40 | return { 41 | login, 42 | userLoggedIn, 43 | userAdmin, 44 | logout, 45 | authUser 46 | } 47 | } 48 | --------------------------------------------------------------------------------
This page can be accesed by guests
This page should only be visible if user is logged in and has admin role