├── .gitignore ├── .nuxtrc ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── package-lock.json ├── package.json ├── playground ├── app.vue ├── nuxt.config.ts ├── package.json ├── pages │ ├── account │ │ └── index.vue │ ├── auth │ │ └── login.vue │ └── index.vue └── tailwind.config.ts ├── src ├── module.ts ├── runtime │ ├── composables.ts │ └── plugin.ts └── types.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .vercel_build_output 23 | .build-* 24 | .env 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode 38 | 39 | # Intellij idea 40 | *.iml 41 | .idea 42 | 43 | # OSX 44 | .DS_Store 45 | .AppleDouble 46 | .LSOverride 47 | .AppleDB 48 | .AppleDesktop 49 | Network Trash Folder 50 | Temporary Items 51 | .apdisk 52 | -------------------------------------------------------------------------------- /.nuxtrc: -------------------------------------------------------------------------------- 1 | imports.autoImport=false 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "bracketSpacing": true, 6 | "bracketSameLine": false, 7 | "vueIndentScriptAndStyle": false, 8 | "printWidth": 80, 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.4.10 4 | 5 | ## v0.4.9 6 | 7 | ## v0.4.7 8 | 9 | - fix runtime config error, now using public runtime config 10 | 11 | ## v0.4.6 12 | 13 | - apiFetch function now return dynamic type 14 | 15 | ### ❤️ Contributors 16 | 17 | - davide-granello 18 | 19 | ## v0.4.5 20 | 21 | ## v0.4.4 22 | 23 | - add ability to change token cookie name (default: `nuxt-sanctum-auth-token`) 24 | - fix request header and cookie name in login function 25 | 26 | ## v0.4.3 27 | 28 | ## v0.4.2 29 | 30 | ### Features 31 | 32 | - Added ability to add custom CSRF keys 33 | 34 | ### ❤️ Contributors 35 | 36 | - Conor-Hughes 37 | 38 | ## v0.4.1 39 | 40 | ## v0.4.0 41 | 42 | ### Features 43 | 44 | - Ability to use JWT token instead of cookie for managing auth 45 | 46 | ### ❤️ Contributors 47 | 48 | - davide-granello 49 | 50 | ## v0.3.2 51 | 52 | - Added type definitions for login, logout and getUser methods 53 | 54 | ### 📖 Documentation 55 | 56 | - Fix typo (dae0935) 57 | 58 | ### ❤️ Contributors 59 | 60 | - Nicolas Hedger 61 | 62 | ## v0.3.1 63 | 64 | - added callback function to `login` and `logout` 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nuxt Sanctum Auth 2 | 3 | ## Notice of Deprecation 4 | 5 | **This package is no longer being developed or maintained.** We recommend using the more up-to-date and actively maintained package [nuxt-auth-sanctum](https://github.com/manchenkoff/nuxt-auth-sanctum) instead. Thank you for your understanding. 6 | 7 | [![npm version](https://badge.fury.io/js/nuxt-sanctum-auth.svg)](https://badge.fury.io/js/nuxt-sanctum-auth) 8 | 9 | This is a simple package for integrating Laravel Sanctum auth with Nuxt3. 10 | This package is in developement and for now works only in **SPA** or **Hybrid** mode. No full SSR support, yet. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | yarn add nuxt-sanctum-auth 16 | # or 17 | npm i nuxt-sanctum-auth 18 | ``` 19 | 20 | Import the module into the `nuxt.config.[js,ts]` and disable `ssr`. 21 | Or alternatively disable `ssr` via `routeRules`, only for pages where `auth` or `guest` middlewares are needed. Typically account section and login page. 22 | 23 | ```js 24 | export default defineNuxtConfig({ 25 | ssr: false, 26 | // or 27 | routeRules: { 28 | '/account/**': { ssr: false }, 29 | '/auth/**': { ssr: false } 30 | }, 31 | 32 | modules: [ 33 | 'nuxt-sanctum-auth' 34 | // ... 35 | ] 36 | }) 37 | ``` 38 | 39 | You can also define options as below (defaults in example): 40 | 41 | ```js 42 | export default defineNuxtConfig({ 43 | // ... 44 | modules: [ 45 | 'nuxt-sanctum-auth' 46 | // ... 47 | ], 48 | nuxtSanctumAuth: { 49 | token: false, // set true to use jwt-token auth instead of cookie. default is false 50 | baseUrl: 'http://localhost:8000', 51 | endpoints: { 52 | csrf: '/sanctum/csrf-cookie', 53 | login: '/login', 54 | logout: '/logout', 55 | user: '/user' 56 | }, 57 | csrf: { 58 | headerKey: 'X-XSRF-TOKEN', 59 | cookieKey: 'XSRF-TOKEN', 60 | tokenCookieKey: 'nuxt-sanctum-auth-token' 61 | }, 62 | redirects: { 63 | home: '/account', 64 | login: '/auth/login', 65 | logout: '/' 66 | } 67 | } 68 | }) 69 | ``` 70 | 71 | ## Usage 72 | 73 | ### Login 74 | 75 | Package provides you with `$sanctumAuth` plugin, which contains `login` and `logout` methods. 76 | 77 | When you log in using the module, it automatically redirects you to the `home` route as defined in the configuration. However, you can also pass a callback function as the second parameter, which will receive the response data as an argument. This can be useful, for example, if you want to fetch additional user data before redirecting them to the application. Just keep in mind that you'll need to handle the redirection manually. 78 | 79 | ```vue 80 | 104 | ``` 105 | 106 | ### Logout 107 | 108 | When you log out, the module will automatically redirect you to the `logout` route as defined in the configuration. However, you can also choose to pass a callback function to handle the redirect yourself. The callback function will receive the response data from the logout request as an argument. Please note that all session data will be deleted by the time the callback is executed. 109 | 110 | ```vue 111 | 125 | ``` 126 | 127 | ### Accessing user 128 | 129 | The module creates a `useAuth()` composable that utilizes `useState('auth')` in the background. You can use it to get access to a user. 130 | 131 | ```vue 132 | 135 | 136 | 146 | ``` 147 | 148 | ### Middleware 149 | 150 | Package automatically provides two middlewares for you to use: `auth` and `guest`. 151 | If you are using `routeRules` make sure to set `ssr: false` for all pages that will be using those middlewares. Please note that those middlewares are not global and are needed to be included on every protected page. Global middlewares are not possible for now, beacuse of avaliability of `hybrid` mode. 152 | 153 | #### Pages available only when not logged in 154 | 155 | ```vue 156 | 161 | ``` 162 | 163 | #### Pages available only when logged in 164 | 165 | ```vue 166 | 171 | ``` 172 | 173 | ### Using JWT-token auth instead of cookie 174 | 175 | If you want to use Laravel Sanctum with JWT token authentication method, 176 | set the `token` property to true in the module configuration. 177 | 178 | ```js 179 | nuxtSanctumAuth: { 180 | token: true 181 | // other properties 182 | } 183 | ``` 184 | 185 | Your Laravel backend should respond on the login endpoint with a json containing property `token`: 186 | 187 | ```json 188 | { 189 | "token": "1|p1tEPICErFs9TpGKjfkz5QcWDi5M4YqJpVLGUwqM" 190 | } 191 | ``` 192 | 193 | Once logged in, the token will be saved in a cookie. 194 | 195 | If you need to access the token, use property of `useAuth()` 196 | 197 | ```vue 198 | 201 | 202 | 208 | ``` 209 | 210 | ### Data fetching 211 | 212 | In guarded pages, you will have to use special fetching method inside `useAsyncData`. This methods is responsible for carrying the XSRF or JWT auth token. 213 | 214 | ```vue 215 | 219 | ``` 220 | 221 | ### Getting user info in pages/components without middleware 222 | 223 | You absolutely can use user information on all pages, even on those that are not guarded by `auth` midleware. 224 | Only downside is that you have to handle potential empty states your self. Typically on ssr pages, because user info is accessable only on client. 225 | 226 | ```vue 227 | 237 | 238 | 245 | ``` 246 | 247 | ## Development 248 | 249 | - Run `npm run dev:prepare` to generate type stubs. 250 | - Use `npm run dev` to start playground in development mode. 251 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-sanctum-auth", 3 | "version": "0.4.10", 4 | "license": "MIT", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "import": "./dist/module.mjs", 9 | "require": "./dist/module.cjs" 10 | } 11 | }, 12 | "main": "./dist/module.cjs", 13 | "types": "./dist/types.d.ts", 14 | "files": [ 15 | "dist" 16 | ], 17 | "scripts": { 18 | "prepack": "nuxt-module-build", 19 | "dev": "nuxi dev playground", 20 | "dev:build": "nuxi build playground", 21 | "dev:prepare": "nuxt-module-build --stub && nuxi prepare playground", 22 | "release": "npm run prepack && changelogen --release && npm publish && git push --follow-tags" 23 | }, 24 | "dependencies": { 25 | "@nuxt/kit": "^3.2.0", 26 | "ofetch": "^1.0.0" 27 | }, 28 | "devDependencies": { 29 | "@nuxt/module-builder": "latest", 30 | "@nuxt/schema": "^3.0.0", 31 | "@nuxtjs/eslint-config-typescript": "^12.0.0", 32 | "@nuxtjs/tailwindcss": "^6.0.1", 33 | "@tailwindcss/forms": "^0.5.3", 34 | "@types/node": "^18.11.18", 35 | "autoprefixer": "^10.4.13", 36 | "changelogen": "^0.4.1", 37 | "nuxt": "^3.12.4", 38 | "postcss": "^8.4.21", 39 | "prettier": "^2.7.1", 40 | "tailwindcss": "^3.2.6" 41 | }, 42 | "description": "This is a simple package for interating Laravel Sanctum auth with Nuxt3. This package is in developement and for now works only in SPA mode (no ssr yet).", 43 | "repository": { 44 | "type": "git", 45 | "url": "git+https://github.com/dystcz/nuxt-sanctum-auth.git" 46 | }, 47 | "keywords": [ 48 | "nuxt3", 49 | "typescript", 50 | "laravel", 51 | "sanctum", 52 | "auth", 53 | "nuxt" 54 | ], 55 | "author": "Paul Cross (https://paulcross.cz/)", 56 | "bugs": { 57 | "url": "https://github.com/dystcz/nuxt-sanctum-auth/issues" 58 | }, 59 | "homepage": "https://github.com/dystcz/nuxt-sanctum-auth#readme" 60 | } 61 | -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt/config' 2 | import nuxtSanctumAuth from '../dist/module' 3 | 4 | export default defineNuxtConfig({ 5 | app: { 6 | head: { 7 | title: 'nuxt-sanctum-auth' 8 | } 9 | }, 10 | 11 | routeRules: { 12 | '/account/**': { ssr: false }, 13 | '/auth/**': { ssr: false } 14 | }, 15 | 16 | //@ts-ignore 17 | modules: [nuxtSanctumAuth, '@nuxtjs/tailwindcss'], 18 | 19 | nuxtSanctumAuth: { 20 | token: false, // set true to test jwt-token auth instead of cookie 21 | baseUrl: 'http://localhost:8000', 22 | endpoints: { 23 | csrf: '/sanctum/csrf-cookie', 24 | login: '/login', 25 | logout: '/logout', 26 | user: '/user' 27 | }, 28 | redirects: { 29 | home: '/account', 30 | login: '/auth/login', 31 | logout: '/' 32 | } 33 | }, 34 | 35 | compatibilityDate: '2024-08-05' 36 | }) -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "nuxt-sanctum-auth-playground" 4 | } 5 | -------------------------------------------------------------------------------- /playground/pages/account/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | -------------------------------------------------------------------------------- /playground/pages/auth/login.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 59 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 48 | -------------------------------------------------------------------------------- /playground/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | module.exports = { 4 | content: [ 5 | './components/**/*.{js,vue,ts}', 6 | './layouts/**/*.vue', 7 | './pages/**/*.vue', 8 | './plugins/**/*.{js,ts}', 9 | './app.vue' 10 | ], 11 | theme: { 12 | extend: {} 13 | }, 14 | plugins: [require('@tailwindcss/forms')] 15 | } 16 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineNuxtModule, 3 | createResolver, 4 | addPlugin, 5 | addImports 6 | } from '@nuxt/kit' 7 | import type { ModuleOptions } from './types' 8 | 9 | const defaults: ModuleOptions = { 10 | token: false, 11 | baseUrl: 'http://localhost:8000', 12 | endpoints: { 13 | csrf: '/sanctum/csrf-cookie', 14 | login: '/login', 15 | logout: '/logout', 16 | user: '/user' 17 | }, 18 | csrf: { 19 | headerKey: 'X-XSRF-TOKEN', 20 | cookieKey: 'XSRF-TOKEN', 21 | tokenCookieKey: 'nuxt-sanctum-auth-token' 22 | }, 23 | redirects: { 24 | home: '/', 25 | login: '/login', 26 | logout: '/login' 27 | } 28 | } 29 | 30 | export default defineNuxtModule({ 31 | meta: { 32 | name: 'nuxt-sanctum-auth', 33 | configKey: 'nuxtSanctumAuth' 34 | }, 35 | defaults, 36 | setup(options, nuxt) { 37 | nuxt.options.runtimeConfig.public.nuxtSanctumAuth = options 38 | const { resolve } = createResolver(import.meta.url) 39 | addPlugin(resolve('./runtime/plugin')) 40 | 41 | addImports({ 42 | name: 'useAuth', 43 | as: 'useAuth', 44 | from: resolve('runtime/composables') 45 | }) 46 | } 47 | }) 48 | -------------------------------------------------------------------------------- /src/runtime/composables.ts: -------------------------------------------------------------------------------- 1 | import { useState } from '#app' 2 | import { Auth } from '../types' 3 | 4 | export function useAuth () { 5 | return useState('auth').value as Auth 6 | } 7 | -------------------------------------------------------------------------------- /src/runtime/plugin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineNuxtPlugin, 3 | addRouteMiddleware, 4 | useState, 5 | useRuntimeConfig, 6 | useCookie 7 | // @ts-ignore 8 | } from '#app' 9 | import { FetchOptions, FetchRequest, ofetch } from 'ofetch' 10 | import { ModuleOptions, Auth, Callback, Csrf } from '../types' 11 | 12 | export default defineNuxtPlugin(async () => { 13 | const auth = useState('auth', () => { 14 | return { 15 | user: null, 16 | loggedIn: false, 17 | token: null 18 | } 19 | }) 20 | 21 | const config: ModuleOptions = useRuntimeConfig().public.nuxtSanctumAuth 22 | 23 | addRouteMiddleware('auth', async () => { 24 | if (config.token) { 25 | getToken() 26 | } 27 | await getUser() 28 | 29 | if (auth.value.loggedIn === false) { 30 | return config.redirects.login 31 | } 32 | }) 33 | 34 | addRouteMiddleware('guest', async () => { 35 | if (config.token) { 36 | getToken() 37 | } 38 | await getUser() 39 | 40 | if (auth.value.loggedIn) { 41 | return config.redirects.home 42 | } 43 | }) 44 | 45 | const apiFetch = (endpoint: FetchRequest, options?: FetchOptions) => { 46 | const fetch = ofetch.create({ 47 | baseURL: config.baseUrl, 48 | credentials: 'include', 49 | headers: { 50 | Accept: 'application/json', 51 | [config.csrf.headerKey]: !config.token 52 | ? useCookie(config.csrf.cookieKey).value 53 | : null, 54 | Authorization: config.token ? 'Bearer ' + auth.value.token : null 55 | } as HeadersInit 56 | }) 57 | 58 | return fetch(endpoint, options) 59 | } 60 | 61 | async function csrf(): Csrf { 62 | await ofetch(config.endpoints.csrf, { 63 | baseURL: config.baseUrl, 64 | credentials: 'include', 65 | method: 'GET', 66 | headers: { 67 | Accept: 'application/json' 68 | } 69 | }) 70 | } 71 | 72 | const getToken = () => { 73 | auth.value.token = useCookie(config.csrf.tokenCookieKey)?.value || null 74 | } 75 | 76 | const setToken = (token: string) => { 77 | useCookie(config.csrf.tokenCookieKey).value = token 78 | } 79 | 80 | const clearToken = () => { 81 | useCookie(config.csrf.tokenCookieKey).value = null 82 | } 83 | 84 | async function getUser(): Promise { 85 | if (auth.value.loggedIn && auth.value.user) { 86 | return auth.value.user as T 87 | } 88 | 89 | try { 90 | const user = await apiFetch(config.endpoints.user) 91 | if (user) { 92 | auth.value.loggedIn = true 93 | auth.value.user = user 94 | return user as T 95 | } 96 | } catch (error) { 97 | // console.log(error) 98 | } 99 | } 100 | 101 | async function login( 102 | data: any, 103 | callback?: Callback | undefined 104 | ): Promise { 105 | if (!config.token) { 106 | await csrf() 107 | } 108 | 109 | try { 110 | const response = await apiFetch(config.endpoints.login, { 111 | method: 'POST', 112 | body: JSON.stringify(data), 113 | headers: { 114 | Accept: 'application/json', 115 | [config.csrf.headerKey]: !config.token 116 | ? useCookie(config.csrf.cookieKey).value 117 | : null, 118 | Authorization: config.token ? 'Bearer ' + auth.value.token : null 119 | } as HeadersInit 120 | }) 121 | 122 | if (config.token && response) { 123 | setToken(response.token) 124 | } 125 | 126 | if (callback !== undefined) { 127 | callback(response) 128 | return 129 | } 130 | window.location.replace(config.redirects.home) 131 | } catch (error: any) { 132 | throw error.data 133 | } 134 | } 135 | 136 | const logout = async (callback?: Callback | undefined): Promise => { 137 | try { 138 | const response = await apiFetch(config.endpoints.logout, { 139 | method: 'POST' 140 | }) 141 | if (callback !== undefined) { 142 | callback(response) 143 | return 144 | } 145 | 146 | window.location.replace(config.redirects.logout) 147 | } catch (error) { 148 | console.log(error) 149 | } finally { 150 | auth.value.loggedIn = false 151 | auth.value.user = null 152 | auth.value.token = null 153 | clearToken() 154 | } 155 | } 156 | 157 | return { 158 | provide: { 159 | apiFetch, 160 | csrf, 161 | sanctumAuth: { 162 | login, 163 | getUser, 164 | logout 165 | } 166 | } 167 | } 168 | }) 169 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { FetchOptions, FetchRequest } from 'ofetch' 2 | 3 | export interface Endpoints { 4 | csrf: string 5 | login: string 6 | logout: string 7 | user: string 8 | } 9 | 10 | export interface Redirects { 11 | home: string 12 | login: string 13 | logout: string 14 | } 15 | 16 | export interface ModuleOptions { 17 | csrf: CSRFSpec 18 | token: boolean 19 | baseUrl: string 20 | endpoints: Endpoints 21 | redirects: Redirects 22 | } 23 | 24 | export interface Auth { 25 | user: any | null 26 | loggedIn: boolean 27 | token: string | null 28 | } 29 | 30 | export interface CSRFSpec { 31 | headerKey: string 32 | cookieKey: string 33 | tokenCookieKey: string 34 | } 35 | 36 | export type ApiFetch = ( 37 | endpoint: FetchRequest, 38 | options?: FetchOptions 39 | ) => Promise 40 | 41 | export type Csrf = Promise 42 | 43 | export type Callback = (response: any) => void 44 | 45 | export interface SanctumAuthPlugin { 46 | login: (data: any, callback?: Callback | undefined) => Promise 47 | logout: (callback?: Callback | undefined) => Promise 48 | getUser(): Promise 49 | } 50 | 51 | // @ts-ignore 52 | declare module 'vue/types/vue' { 53 | interface Vue { 54 | $sanctumAuth: SanctumAuthPlugin 55 | } 56 | } 57 | 58 | // Nuxt Bridge & Nuxt 3 59 | declare module '#app' { 60 | interface NuxtApp extends PluginInjection {} 61 | } 62 | 63 | interface PluginInjection { 64 | $sanctumAuth: SanctumAuthPlugin 65 | $apiFetch: ApiFetch 66 | $csrf: Csrf 67 | } 68 | 69 | declare module '@vue/runtime-core' { 70 | interface ComponentCustomProperties extends PluginInjection {} 71 | } 72 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./playground/.nuxt/tsconfig.json" 3 | } 4 | --------------------------------------------------------------------------------