Repository: manchenkoff/nuxt-auth-sanctum Branch: main Commit: 7d61bff7d053 Files: 155 Total size: 296.5 KB Directory structure: gitextract_4wmesn7w/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── config.yml │ │ └── feature.yml │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── docs.yml │ ├── prerelease.yml │ ├── publish.yml │ ├── upgrade.yml │ └── validation.yml ├── .gitignore ├── .nuxtrc ├── .yamlfmt.yaml ├── .yamllint.yaml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── TROUBLESHOOTING.md ├── docs/ │ ├── .env.example │ ├── .gitignore │ ├── .npmrc │ ├── README.md │ ├── app/ │ │ ├── app.config.ts │ │ ├── app.vue │ │ ├── assets/ │ │ │ └── css/ │ │ │ └── main.css │ │ ├── components/ │ │ │ ├── AppFooter.vue │ │ │ ├── AppHeader.vue │ │ │ ├── AppLogo.vue │ │ │ ├── OgImage/ │ │ │ │ └── OgImageDocs.takumi.vue │ │ │ └── content/ │ │ │ ├── HeroBackground.vue │ │ │ └── StarsBackground.vue │ │ ├── error.vue │ │ ├── layouts/ │ │ │ └── docs.vue │ │ └── pages/ │ │ ├── [...slug].vue │ │ └── index.vue │ ├── content/ │ │ ├── 1.getting-started/ │ │ │ ├── .navigation.yml │ │ │ ├── 1.index.md │ │ │ └── 2.installation.md │ │ ├── 2.usage/ │ │ │ ├── 1.configuration.md │ │ │ ├── 2.cookie.md │ │ │ ├── 3.token.md │ │ │ └── 4.proxy.md │ │ ├── 3.composables/ │ │ │ ├── 1.useSanctumAuth.md │ │ │ ├── 2.useSanctumUser.md │ │ │ ├── 3.useSanctumClient.md │ │ │ ├── 4.useSanctumFetch.md │ │ │ ├── 5.useLazySanctumFetch.md │ │ │ ├── 6.useSanctumConfig.md │ │ │ └── 7.useSanctumAppConfig.md │ │ ├── 4.middleware/ │ │ │ ├── 1.sanctum-auth.md │ │ │ ├── 2.sanctum-guest.md │ │ │ └── 3.global.md │ │ ├── 5.hooks/ │ │ │ ├── 1.sanctum-request.md │ │ │ ├── 10.sanctum-proxy-request.md │ │ │ ├── 2.sanctum-response.md │ │ │ ├── 3.sanctum-error-request.md │ │ │ ├── 4.sanctum-error-response.md │ │ │ ├── 5.sanctum-redirect.md │ │ │ ├── 6.sanctum-init.md │ │ │ ├── 7.sanctum-refresh.md │ │ │ ├── 8.sanctum-login.md │ │ │ └── 9.sanctum-logout.md │ │ ├── 6.advanced/ │ │ │ ├── 1.interceptors.md │ │ │ ├── 2.error-handling.md │ │ │ ├── 3.logging.md │ │ │ ├── 4.token-storage.md │ │ │ ├── 5.dependencies.md │ │ │ ├── 6.breeze-nuxt-template.md │ │ │ └── 7.troubleshooting.md │ │ └── index.md │ ├── content.config.ts │ ├── eslint.config.mjs │ ├── nuxt.config.ts │ ├── package.json │ ├── pnpm-workspace.yaml │ ├── renovate.json │ └── tsconfig.json ├── eslint.config.mjs ├── package.json ├── playground/ │ ├── app/ │ │ ├── app.vue │ │ ├── error.vue │ │ ├── layouts/ │ │ │ └── default.vue │ │ ├── pages/ │ │ │ ├── index.vue │ │ │ ├── login.vue │ │ │ ├── logout.vue │ │ │ ├── profile.vue │ │ │ └── welcome.vue │ │ └── plugins/ │ │ ├── sanctum.hooks.ts │ │ └── sanctum.storage.client.ts │ ├── nuxt.config.ts │ ├── package.json │ ├── pnpm-workspace.yaml │ ├── server/ │ │ ├── plugins/ │ │ │ └── sanctum.hooks.ts │ │ └── tsconfig.json │ └── tsconfig.json ├── src/ │ ├── config.ts │ ├── module.ts │ ├── runtime/ │ │ ├── composables/ │ │ │ ├── useLazySanctumFetch.ts │ │ │ ├── useSanctumAppConfig.ts │ │ │ ├── useSanctumAuth.ts │ │ │ ├── useSanctumClient.ts │ │ │ ├── useSanctumConfig.ts │ │ │ ├── useSanctumFetch.ts │ │ │ ├── useSanctumTokenStorage.ts │ │ │ └── useSanctumUser.ts │ │ ├── httpFactory.ts │ │ ├── interceptors/ │ │ │ ├── index.ts │ │ │ ├── request/ │ │ │ │ ├── logging.ts │ │ │ │ ├── params.ts │ │ │ │ ├── stateful.ts │ │ │ │ └── token.ts │ │ │ └── response/ │ │ │ ├── errorHandler.ts │ │ │ ├── logging.ts │ │ │ ├── proxy.ts │ │ │ └── validation.ts │ │ ├── middleware/ │ │ │ ├── sanctum.auth.ts │ │ │ ├── sanctum.global.ts │ │ │ └── sanctum.guest.ts │ │ ├── plugin.ts │ │ ├── server/ │ │ │ ├── api/ │ │ │ │ └── proxy.ts │ │ │ └── augments.server.d.ts │ │ ├── storages/ │ │ │ └── cookieTokenStorage.ts │ │ ├── types/ │ │ │ ├── config.ts │ │ │ ├── fetch.ts │ │ │ ├── meta.ts │ │ │ └── options.ts │ │ └── utils/ │ │ ├── constants.ts │ │ ├── credentials.ts │ │ ├── formatter.ts │ │ ├── logging.ts │ │ ├── runtime.ts │ │ └── session.ts │ └── templates.ts ├── test/ │ ├── helpers/ │ │ ├── constants.ts │ │ └── mocks.ts │ └── unit/ │ ├── config.test.ts │ ├── constants.test.ts │ ├── credentials.test.ts │ ├── formatter.test.ts │ ├── interceptors/ │ │ ├── request/ │ │ │ ├── logging.test.ts │ │ │ ├── params.test.ts │ │ │ ├── stateful.test.ts │ │ │ └── token.test.ts │ │ └── response/ │ │ ├── errorHandler.test.ts │ │ ├── logging.test.ts │ │ ├── proxy.test.ts │ │ └── validation.test.ts │ ├── logging.test.ts │ └── runtime.test.ts ├── tsconfig.json └── vitest.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ github: manchenkoff thanks_dev: manchenkoff buy_me_a_coffee: manchenkoff tidelift: npm/nuxt-auth-sanctum ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: Bug Report description: Report a bug or incorrect behaviour title: "[bug] name" labels: bug assignees: manchenkoff body: - id: problem_statement type: textarea attributes: label: Describe the bug description: Provide a clear and concise description of the bug placeholder: The feature X might be broken when I do Y validations: required: true - id: expected_behaviour type: textarea attributes: label: Expected behaviour description: Provide a clear description of what you expected to happen placeholder: When I do X, I expect Y to happen validations: required: true - id: actual_behaviour type: textarea attributes: label: Actual behaviour description: Provide a concise description of what actually happened placeholder: When I do X, Y happens instead validations: required: true - id: runtime type: dropdown attributes: label: Runtime description: Choose affected runtimes options: - Client side (CSR) - Server side (SSR) - Both default: 0 validations: required: true - id: environment type: dropdown attributes: label: Environment description: Choose affected environments options: - Development (local) - Production - Both default: 1 validations: required: true - id: reproduction type: textarea attributes: label: Reproduction steps description: | Please explain how to reproduce the bug, ideally with a minimal code example or repository placeholder: | 1. Do X 2. Do Y 3. See Z happening validations: required: false - id: nuxt_version type: input attributes: label: Nuxt version description: Version of the Nuxt framework placeholder: 4.2.1 validations: required: true - id: module_version type: input attributes: label: Module version description: Version of the module installed placeholder: 1.0.1 validations: required: true - id: config_example type: textarea attributes: label: Module config description: Provide your `nuxt.config.ts` contents render: typescript validations: required: false - type: upload id: file_upload attributes: label: File Upload description: Attach a file to this issue (screenshot, log file, etc.) validations: required: false - id: investigation type: checkboxes attributes: label: Initial investigation options: - label: I have checked the documentation, no solution found required: true - label: I have checked the troubleshooting guide, did not help required: true ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false ================================================ FILE: .github/ISSUE_TEMPLATE/feature.yml ================================================ name: Feature Request description: Suggest an idea for this project title: "[feature] name" labels: enhancement assignees: manchenkoff body: - id: feature_description type: textarea attributes: label: Describe the feature description: Provide a detailed description of the functionality value: | It would be nice to have a way to ... validations: required: true - id: proposed_solution type: textarea attributes: label: Proposed solution description: Describe the solution you would like to see placeholder: I think this can be implemented in the way... validations: required: false - id: alternative_solution type: textarea attributes: label: Alternatives description: Describe alternatives you have considered placeholder: Another option would be... validations: required: false - id: ready_to_contribute type: checkboxes attributes: label: Do you want to contribute? description: | Check this box if you are happy to implement this yourself once we have a plan options: - label: "Yes" required: false ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "npm" directory: "/" schedule: interval: "monthly" ================================================ FILE: .github/pull_request_template.md ================================================ **Describe the problem and solution** Closes #XXX. REPLACE_THIS_WITH_YOUR_DESCRIPTION ================================================ FILE: .github/workflows/docs.yml ================================================ name: Nuxt [Docs] env: node_version: 24 concurrency: group: "pages" cancel-in-progress: false on: push: branches: ["main"] paths: ["docs/**"] workflow_dispatch: permissions: contents: read pages: write id-token: write jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.node_version }} cache: pnpm - name: Install dependencies working-directory: ./docs run: pnpm install - name: Validate working-directory: ./docs run: pnpm lint - name: Generate documentation run: pnpm generate working-directory: ./docs - name: Upload artifact uses: actions/upload-pages-artifact@v4 with: path: ./docs/.output/public deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v5 ================================================ FILE: .github/workflows/prerelease.yml ================================================ name: Nuxt [Pre-Release] env: node_version: 24 node_registry: https://registry.npmjs.org/ changelog_user: Github CI changelog_email: artem@manchenkoff.me GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} concurrency: group: nuxt-auth-sanctum-prerelease cancel-in-progress: false permissions: contents: write id-token: write on: workflow_dispatch jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.node_version }} cache: pnpm - name: Install dependencies run: pnpm install - name: Prepare stubs run: pnpm dev:prepare - name: Validate package run: pnpm validate publish: runs-on: ubuntu-latest needs: lint steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install pnpm uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.node_version }} registry-url: ${{ env.node_registry }} cache: pnpm - name: Install dependencies run: pnpm install - name: Prepare stubs run: pnpm dev:prepare - name: Build run: pnpm prepack - name: Generate changelog and publish release run: |- git config --global user.name "${{ env.changelog_user }}" git config --global user.email "${{ env.changelog_email }}" pnpm changelogen --release --push \ --publish --prerelease --publishTag beta ================================================ FILE: .github/workflows/publish.yml ================================================ name: Nuxt [Release] env: node_version: 24 node_registry: https://registry.npmjs.org/ changelog_user: Github CI changelog_email: artem@manchenkoff.me GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} concurrency: group: nuxt-auth-sanctum-release cancel-in-progress: false permissions: contents: write id-token: write on: workflow_dispatch jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.node_version }} cache: pnpm - name: Install dependencies run: pnpm install - name: Prepare stubs run: pnpm dev:prepare - name: Validate package run: pnpm validate publish: runs-on: ubuntu-latest needs: lint steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Install pnpm uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.node_version }} registry-url: ${{ env.node_registry }} cache: pnpm - name: Install dependencies run: pnpm install - name: Prepare stubs run: pnpm dev:prepare - name: Build run: pnpm prepack - name: Generate changelog and publish release run: |- git config --global user.name "${{ env.changelog_user }}" git config --global user.email "${{ env.changelog_email }}" pnpm changelogen --release --push --publish ================================================ FILE: .github/workflows/upgrade.yml ================================================ name: Upgrade Dependencies env: node_version: 24 git_email: artem@manchenkoff.me git_name: manchenkoff on: workflow_dispatch: schedule: - cron: "0 0 * * 0" jobs: upgrade: runs-on: ubuntu-latest permissions: contents: write pull-requests: write strategy: fail-fast: false matrix: project: [".", "docs", "playground"] steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: ${{ env.node_version }} - name: Enable corepack run: corepack enable - name: Install dependencies working-directory: ${{ matrix.project }} run: npx nypm@latest i - name: Upgrade working-directory: ${{ matrix.project }} run: pnpm upgrade - name: Check for changes id: changed run: | echo "DATE=$(date +%Y-%m-%d)" >> $GITHUB_ENV PROJECT_NAME="${{ matrix.project }}" if [ "$PROJECT_NAME" = "." ]; then PROJECT_NAME="root" fi echo "project_name=$PROJECT_NAME" >> $GITHUB_ENV if git diff --quiet; then echo "changed=false" >> $GITHUB_OUTPUT else echo "changed=true" >> $GITHUB_OUTPUT fi - name: Configure git if: steps.changed.outputs.changed == 'true' run: | git config --global user.email "${{ env.git_email }}" git config --global user.name "${{ env.git_name }}" - name: Create branch and commit if: steps.changed.outputs.changed == 'true' run: | BRANCH="deps/bump-${DATE}-${{ env.project_name }}" git checkout -b "$BRANCH" git add -A git commit -m "chore: upgrade ${{ env.project_name }} dependencies" - name: Push changes if: steps.changed.outputs.changed == 'true' run: | BRANCH="deps/bump-${DATE}-${{ env.project_name }}" git push origin :$BRANCH || true git push -u origin HEAD - name: Create Pull Request if: steps.changed.outputs.changed == 'true' run: | gh pr create \ --base main \ --head deps/bump-${DATE}-${{ env.project_name }} \ --title "deps: ${{ env.project_name }} weekly dependency bump" \ --body "Automated dependency upgrade for ${{ env.project_name }}. Please review before merging." \ --assignee manchenkoff env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/validation.yml ================================================ name: Nuxt [Validate] env: node_version: 24 concurrency: group: nuxt-auth-sanctum-ci cancel-in-progress: false on: workflow_dispatch: pull_request: branches: [main] permissions: contents: read jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.node_version }} cache: pnpm - name: Install dependencies run: pnpm install - name: Build stubs run: pnpm dev:prepare - name: Lint run: pnpm lint - name: Type check run: pnpm test:types test: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: ${{ env.node_version }} cache: pnpm - name: Install dependencies run: pnpm install - name: Build stubs run: pnpm dev:prepare - name: Test run: pnpm test ================================================ FILE: .gitignore ================================================ # Dependencies node_modules # Logs *.log # Temp directories .temp .tmp .cache # Yarn **/.yarn/cache **/.yarn/*state* # Generated dirs dist # Nuxt .nuxt .output .data .vercel_build_output .build-* .netlify # Env .env # Testing reports coverage *.lcov .nyc_output # VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets # Intellij idea *.iml .idea # OSX .DS_Store .AppleDouble .LSOverride .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ================================================ FILE: .nuxtrc ================================================ setups.@nuxt/test-utils="4.0.2" ================================================ FILE: .yamlfmt.yaml ================================================ # https://github.com/google/yamlfmt/blob/main/docs/config-file.md formatter: type: basic include_document_start: false ================================================ FILE: .yamllint.yaml ================================================ # https://yamllint.readthedocs.io/en/stable/rules.html extends: default rules: document-start: present: false truthy: false ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## v2.3.4 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.3.3...v2.3.4) ### 🩹 Fixes - Use correct config in logger and improve docs about env overrides ([#605](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/605)) ### 📖 Documentation - Clarify SSR environment variable requirements for baseUrl ([#595](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/595)) ### 🏡 Chore - **test:** Added module unit tests ([#602](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/602)) ### ❤️ Contributors - Artem Manchenkov ([@manchenkoff](https://github.com/manchenkoff)) - Derrick Obedgiu ## v2.3.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.3.2...v2.3.3) ### 🩹 Fixes - **proxy:** Use runtime-agnostic readRawBody for multipart file uploads ([#582](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/582)) ### 🏡 Chore - **ci:** Upgraded action versions ([daffcee](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/daffcee)) ### ❤️ Contributors - Derrick Obedgiu - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v2.3.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.3.1...v2.3.2) ### 🩹 Fixes - Fallback to default token storage in both csr and ssr ([#580](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/580)) - Allow partial override for runtime config ([#579](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/579)) - Deduplicate fetch requests using key ([#581](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/581)) ### 🏡 Chore - Upgraded nuxt and content setup ([ad62713](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ad62713)) - Upgraded nuxt and content setup ([bb7e9a2](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/bb7e9a2)) ### ❤️ Contributors - Artem Manchenkov ([@manchenkoff](https://github.com/manchenkoff)) - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v2.3.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.3.0...v2.3.1) ### 🩹 Fixes - Typo in Configuration.md ([#569](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/569)) ### 🏡 Chore - Updated issue templates ([53a4323](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/53a4323)) - Added error logs for debugging ([#570](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/570)) ### ❤️ Contributors - Artem Manchenkov ([@manchenkoff](https://github.com/manchenkoff)) - EtienneHosman ([@EtienneHosman](https://github.com/EtienneHosman)) - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v2.3.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.2.0...v2.3.0) ### 🚀 Enhancements - Added token storage hook ([#553](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/553)) - Split server and client configs ([#552](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/552)) ### ❤️ Contributors - Artem Manchenkov ([@manchenkoff](https://github.com/manchenkoff)) ## v2.2.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.1.3...v2.2.0) ### 🚀 Enhancements - Sanitize proxy request headers to exclude hop-by-hop ([#525](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/525)) ### 📖 Documentation - Added plugin-based approach for token storage ([#539](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/539)) ### 🏡 Chore - **deps-dev:** Bump @types/node from 25.1.0 to 25.3.3 ([#534](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/534)) - **deps-dev:** Bump @nuxt/kit from 4.2.2 to 4.3.1 ([#537](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/537)) - **deps-dev:** Bump nuxt from 4.3.0 to 4.3.1 ([#538](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/538)) - **deps-dev:** Bump @nuxt/test-utils from 3.23.0 to 4.0.0 ([#535](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/535)) - **deps-dev:** Bump @nuxt/devtools from 3.1.1 to 3.2.2 ([#536](https://github.com/manchenkoff/nuxt-auth-sanctum/pull/536)) ### ❤️ Contributors - Ahoiroman - Artem Manchenkov ([@manchenkoff](https://github.com/manchenkoff)) ## v2.1.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.1.2...v2.1.3) ### 🩹 Fixes - **proxy:** Prevent readBody crash on empty DELETE requests ([2ef0977](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2ef0977)) ### 🏡 Chore - **deps-dev:** Bump @types/node from 25.0.9 to 25.1.0 ([888afbe](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/888afbe)) - **deps-dev:** Bump @nuxt/schema from 4.2.2 to 4.3.0 ([dadb7ab](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/dadb7ab)) - **deps-dev:** Bump vitest from 4.0.17 to 4.0.18 ([2b8df57](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2b8df57)) - **deps-dev:** Bump vue-tsc from 3.2.2 to 3.2.4 ([14f3b99](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/14f3b99)) - **deps-dev:** Bump nuxt from 4.2.2 to 4.3.0 ([327ac03](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/327ac03)) - Fix lints ([7d13b94](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/7d13b94)) ### ❤️ Contributors - Derrick Obedgiu ## v2.1.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.1.1...v2.1.2) ### 🩹 Fixes - Do not read body on multipart requests ([fcbff01](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fcbff01)) ### 🏡 Chore - Bump packages ([dda16b8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/dda16b8)) - Bump packages ([15dc3b5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/15dc3b5)) - Added certificated.dev banner ([e951e56](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e951e56)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v2.1.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.1.0...v2.1.1) ### 🩹 Fixes - Use new release pipeline workflow ([0beadd5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0beadd5)) - **proxy:** Use runtime-agnostic readBody for v8-engine compatibility ([47d6523](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/47d6523)) ### ❤️ Contributors - Derrick Obedgiu - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v2.1.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v2.0.0...v2.1.0) ### 🚀 Enhancements - Added server hook for proxy request ([1ac5481](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1ac5481)) ### 🏡 Chore - Minor refactoring for proxy endpoint ([8074e14](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8074e14)) - Added playground example for nitro hook plugin ([dbd2b85](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/dbd2b85)) - **docs:** Added description for proxy hook ([dd2d1ce](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/dd2d1ce)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v2.0.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.3...v2.0.0) ### 🚀 Enhancements - Add optional fetch options to login/logout methods ([981d5cc](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/981d5cc)) - ⚠️ Migrate to useFetch composable ([25f9af5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/25f9af5)) - ⚠️ Removed checkSession from public scope ([ac34929](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ac34929)) ### 🏡 Chore - **deps-dev:** Bump vue from 3.5.24 to 3.5.25 ([7700fa7](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/7700fa7)) - **deps-dev:** Bump vitest from 4.0.13 to 4.0.14 ([f9474dd](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f9474dd)) - **deps-dev:** Bump @nuxt/eslint-config from 1.10.0 to 1.11.0 ([3a24c01](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/3a24c01)) - **deps-dev:** Bump @nuxt/devtools from 3.1.0 to 3.1.1 ([d7e0f2b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/d7e0f2b)) - **docs:** Updated info about useSanctumAuth composable ([34c54c7](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/34c54c7)) - **docs:** Updated info about useSanctumAuth composable ([f092852](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f092852)) - **docs:** Adjusted composables description ([8c958e2](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8c958e2)) #### ⚠️ Breaking Changes - ⚠️ Migrate to useFetch composable ([25f9af5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/25f9af5)) - ⚠️ Removed checkSession from public scope ([ac34929](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ac34929)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.4.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.2...v1.4.3) ### 🏡 Chore - **deps-dev:** Bump @nuxt/devtools from 2.7.0 to 3.0.1 ([ae873cf](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ae873cf)) - **docs:** Updated readme ([a68d9a6](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/a68d9a6)) - Remove outdated type augmentation ([5df0223](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5df0223)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.4.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.1...v1.4.2) ## v1.4.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.1-4...v1.4.1) ### 🩹 Fixes - Avoid memory leak on session check and prevent infinite loop ([0d7ca68](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0d7ca68)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.4.1-4 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.1-3...v1.4.1-4) ### 🩹 Fixes - Move session check outside of composable ([5d7e52a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5d7e52a)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.4.1-3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.1-2...v1.4.1-3) ### 🩹 Fixes - Disable session check ([745ed5a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/745ed5a)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.4.1-2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.1-1...v1.4.1-2) ### 🩹 Fixes - Enable reactive resolver for fetch key ([6daa5d4](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6daa5d4)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.4.1-1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.1-0...v1.4.1-1) ### 🩹 Fixes - Enable cookie check without reactivity ([68e13fd](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/68e13fd)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.4.1-0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.4.0...v1.4.1-0) ### 🩹 Fixes - Use raw data for serialization and disable session check ([11523c2](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/11523c2)) ### 🏡 Chore - **docs:** Updated website url ([848e0f9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/848e0f9)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.4.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.3.1...v1.4.0) ### 🚀 Enhancements - Added reactive for useAsyncData behind sanctumFetch ([bd94165](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/bd94165)) - Added reactive url for useAsyncData behind sanctumFetch ([b471100](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b471100)) ### 📖 Documentation - Improved nuxt content configuration and updated docs ([bf7d2e8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/bf7d2e8)) ### 🏡 Chore - **deps-dev:** Bump vue-tsc from 2.2.12 to 3.1.0 ([6401d22](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6401d22)) - **docs:** Fixed typo ([90b6565](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/90b6565)) - Added yaml linter configs ([19589d0](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/19589d0)) - **ci:** Trigger docs deployment only if changed ([c202bc3](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c202bc3)) - **docs:** Added section abour reactivity for fetch key/url ([81c2ca2](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/81c2ca2)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.3.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.3.0...v1.3.1) ### 🩹 Fixes - Resolve reactive options in useAsyncData ([95b3166](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/95b3166)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.3.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.2.0...v1.3.0) ### 🚀 Enhancements - Check session existence in middleware ([217c60e](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/217c60e)) ### 🏡 Chore - **deps-dev:** Bump nuxt in the npm_and_yarn group across 1 directory ([146faed](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/146faed)) ### 🤖 CI - Simplify publish release ([e2c8fe5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e2c8fe5)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.2.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.1.2...v1.2.0) ### 🚀 Enhancements - Support user-defined fetch key ([b124fa8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b124fa8)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.1.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.1.1...v1.1.2) ### 🩹 Fixes - Handle no connection error ([5b9bb04](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5b9bb04)) ### 🏡 Chore - **deps-dev:** Bump @nuxt/schema from 4.0.1 to 4.0.2 ([e0f12bd](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e0f12bd)) - **deps-dev:** Bump eslint from 9.31.0 to 9.32.0 ([66f5de9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/66f5de9)) - **deps-dev:** Bump @nuxt/module-builder from 1.0.1 to 1.0.2 ([f6f6d01](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f6f6d01)) - **deps-dev:** Bump @nuxt/eslint-config from 1.7.0 to 1.7.1 ([e48a4be](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e48a4be)) - **deps-dev:** Bump @nuxt/kit from 4.0.1 to 4.0.2 ([206a7c5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/206a7c5)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.1.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.1.0...v1.1.1) ### 🩹 Fixes - Upgraded vulnerable package ([fd4acf1](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fd4acf1)) ### 🏡 Chore - Upgraded to nuxt 4 ([34450d7](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/34450d7)) - Bump packages ([2155122](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2155122)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.1.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.0.1...v1.1.0) ### 🚀 Enhancements - Allow login without fetching an identity ([55df80c](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/55df80c)) - Hide serverProxy endpoint from runtime config ([a730c4c](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/a730c4c)) ### 🩹 Fixes - Proxy request without reading ([18b6105](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/18b6105)) ### 🏡 Chore - **deps-dev:** Bump @types/node from 22.15.32 to 24.0.8 ([c193a81](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c193a81)) - **deps-dev:** Bump @nuxt/devtools from 2.5.0 to 2.6.0 ([5a284b4](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5a284b4)) - **deps-dev:** Bump eslint from 9.29.0 to 9.30.0 ([0a5e885](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0a5e885)) - Bumped packages and apply formatting ([c16c843](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c16c843)) - Added approved build ([c75bd7d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c75bd7d)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.0.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v1.0.0...v1.0.1) ### 🩹 Fixes - Ignore reprocessed response content headers ([861fdfd](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/861fdfd)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v1.0.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.6.7...v1.0.0) ### 🚀 Enhancements - **types:** ⚠️ Added explicit types for fetch client ([6054510](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6054510)) - **hooks:** ⚠️ Migrated interceptors to hooks ([3d23818](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/3d23818)) - Added nuxt server proxy endpoint ([4a32314](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/4a32314)) ### 🩹 Fixes - **types:** ⚠️ Drop old app types augmentation ([4b54597](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/4b54597)) ### 🏡 Chore - **deps-dev:** Bump nuxt from 3.17.3 to 3.17.4 ([ce65d87](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ce65d87)) #### ⚠️ Breaking Changes - **types:** ⚠️ Added explicit types for fetch client ([6054510](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6054510)) - **hooks:** ⚠️ Migrated interceptors to hooks ([3d23818](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/3d23818)) - **types:** ⚠️ Drop old app types augmentation ([4b54597](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/4b54597)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v0.6.7 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.6.6...v0.6.7) ### 🩹 Fixes - Use proper typing for lazy fetch ([9c124dc](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9c124dc)) - Use github action permissions ([1ced700](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1ced700)) ### 🏡 Chore - **deps-dev:** Bump @types/node from 22.14.0 to 22.15.3 ([5de3e8f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5de3e8f)) - **deps-dev:** Bump @nuxt/kit from 3.16.2 to 3.17.1 ([6bf9aa1](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6bf9aa1)) - **deps-dev:** Bump nuxt from 3.16.2 to 3.17.1 ([1caa2ce](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1caa2ce)) - **deps-dev:** Bump @nuxt/schema from 3.16.2 to 3.17.1 ([95e2233](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/95e2233)) - Upgraded dependencies ([427ced5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/427ced5)) - Upgraded nuxt module builder ([511d6fc](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/511d6fc)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v0.6.6 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.6.5...v0.6.6) ### 🩹 Fixes - Removed redundant logging ([29fb5ba](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/29fb5ba)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v0.6.5 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.6.4...v0.6.5) ### 🩹 Fixes - Avoid mutable list of interceptors ([333f7c8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/333f7c8)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v0.6.4 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.6.3...v0.6.4) ### 🚀 Enhancements - **hook:** Added onRequestError event `sanctum:error:request` hook ([b5095e5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b5095e5)) ### 🏡 Chore - Restructured project ([161dc4e](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/161dc4e)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v0.6.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.6.2...v0.6.3) ### 🚀 Enhancements - **client:** Allow overriding of Accept header per request ([871fcae](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/871fcae)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v0.6.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.6.1...v0.6.2) ### 🚀 Enhancements - Return login response ([d341431](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/d341431)) ### 🏡 Chore - **deps-dev:** Bump @nuxt/devtools from 2.3.1 to 2.3.2 ([5d8442f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5d8442f)) - **deps-dev:** Bump vitest from 3.0.9 to 3.1.1 ([71f7049](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/71f7049)) - **deps-dev:** Bump @types/node from 22.13.13 to 22.13.16 ([4273f0f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/4273f0f)) ### ❤️ Contributors - SwiTool ## v0.6.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.6.0...v0.6.1) ### 🚀 Enhancements - **logs:** Added more details into logs for troubleshooting ([b7331c9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b7331c9)) - **tests:** Added test fixture project to simulate remote API ([27a7976](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/27a7976)) ### 🩹 Fixes - **init:** Use raw fetch to handle errors on identity init ([c1321bf](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c1321bf)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v0.6.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.8...v0.6.0) ### 🚀 Enhancements - **hooks:** Added sanctum:error hook ([413989f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/413989f)) - **hooks:** Added sanctum:redirect hook ([c7bf355](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c7bf355)) - **hooks:** Added sanctum:init and sanctum:refresh hooks ([fa5b592](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fa5b592)) - **hooks:** Added sanctum:login hook ([ad67393](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ad67393)) - **hooks:** Added sanctum:logout hook ([dd496cf](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/dd496cf)) - **fetch:** ⚠️ Support AsyncDataOptions in sanctum fetch composables ([9f23351](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9f23351)) ### 🏡 Chore - Upgraded nuxt packages ([0df572b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0df572b)) - Upgraded changelogen package ([03c8b31](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/03c8b31)) - Added troubleshooting guide link ([2bd41a1](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2bd41a1)) - Upgraded nuxt dependencies ([5a34273](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5a34273)) #### ⚠️ Breaking Changes - **fetch:** ⚠️ Support AsyncDataOptions in sanctum fetch composables ([9f23351](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9f23351)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](https://github.com/manchenkoff)) ## v0.5.8 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.7...v0.5.8) ### 🩹 Fixes - Use unique key for sanctum fetch across csr/ssr ([0d43ea6](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0d43ea6)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.5.7 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.6...v0.5.7) ### 🩹 Fixes - Migrated from useFetch to useAsyncData ([79cb27d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/79cb27d)) ### 🏡 Chore - Dependencies upgrade ([2bbe90c](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2bbe90c)) - **dev-deps:** Upgraded dev dependencies ([6a629df](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6a629df)) - **dev-deps:** Upgraded nuxt devtools ([bc3b601](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/bc3b601)) - **security:** Upgraded esbuild to avoid security issues ([82ad135](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/82ad135)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.5.6 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.5...v0.5.6) ### 🚀 Enhancements - Added new composables to make requests ([a6bfc2f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/a6bfc2f)) ### 🩹 Fixes - Added type hints for fetch composables ([bbb9e04](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/bbb9e04)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.5.5 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.4...v0.5.5) ### 🩹 Fixes - Use npm keys in actions ([9bc984f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9bc984f)) - Deduplicate `set-cookie` headers on SSR response ([9d957e2](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9d957e2)) - Downgraded typescript to fix module-builder ([f28daf8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f28daf8)) ### 🏡 Chore - **deps-dev:** Bump @nuxt/devtools from 1.6.4 to 1.7.0 ([b58ebc4](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b58ebc4)) - **deps-dev:** Bump @nuxt/eslint-config from 0.7.3 to 0.7.4 ([0f79c42](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0f79c42)) - **deps-dev:** Bump @typescript-eslint/eslint-plugin ([0cae715](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0cae715)) - **deps-dev:** Bump @nuxt/schema from 3.14.1592 to 3.15.0 ([77c79f6](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/77c79f6)) - **deps-dev:** Bump @nuxt/test-utils from 3.15.1 to 3.15.4 ([2cc446b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2cc446b)) - **deps-dev:** Bump vitest in the npm_and_yarn group ([b64a354](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b64a354)) - **deps-dev:** Bump @nuxt/schema from 3.15.2 to 3.15.4 ([c2dd453](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c2dd453)) - **deps-dev:** Bump @nuxt/eslint-config from 0.7.4 to 1.0.0 ([a435516](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/a435516)) - Upgraded nuxt packages ([e0c5bb9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e0c5bb9)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.5.4 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.3...v0.5.4) ### 🩹 Fixes - Use relative paths for injected types ([63696ff](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/63696ff)) ### ❤️ Contributors - Denis Mamaev ([@ExileofAranei](http://github.com/ExileofAranei)) ## v0.5.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.2...v0.5.3) ### 🩹 Fixes - Expose plugin name to be used in dependsOn ([c2d930b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c2d930b)) ### 🏡 Chore - **deps-dev:** Bump @nuxt/schema from 3.14.159 to 3.14.1592 ([83be062](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/83be062)) - **deps-dev:** Bump nuxt from 3.14.159 to 3.14.1592 ([0469f64](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0469f64)) - **deps-dev:** Bump @typescript-eslint/eslint-plugin ([f619be8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f619be8)) - **deps-dev:** Bump eslint from 9.15.0 to 9.16.0 ([464c8ed](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/464c8ed)) - **deps-dev:** Bump @nuxt/eslint-config from 0.7.0 to 0.7.2 ([38c1c46](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/38c1c46)) - **deps-dev:** Bump @nuxt/devtools from 1.6.1 to 1.6.3 ([31a8027](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/31a8027)) - **deps-dev:** Bump @typescript-eslint/eslint-plugin ([1d47a17](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1d47a17)) - **deps-dev:** Bump vitest from 2.1.5 to 2.1.8 ([63fa544](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/63fa544)) - **deps-dev:** Bump @nuxt/test-utils from 3.14.4 to 3.15.1 ([7c21b35](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/7c21b35)) - **deps-dev:** Bump @types/node from 22.10.1 to 22.10.2 ([b1d429b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b1d429b)) - **deps-dev:** Bump @typescript-eslint/eslint-plugin ([3067b61](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/3067b61)) - **deps-dev:** Bump eslint from 9.16.0 to 9.17.0 ([ce17914](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ce17914)) - **deps-dev:** Bump @nuxt/eslint-config from 0.7.2 to 0.7.3 ([2ff5e99](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2ff5e99)) - Upgraded playground to nuxt v4 structure ([056b488](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/056b488)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.5.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.1...v0.5.2) ### 🩹 Fixes - Support insecure cookies for auth token ([5e59064](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5e59064)) ### 🏡 Chore - **deps-dev:** Bump @types/node from 22.7.7 to 22.8.1 ([979c480](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/979c480)) - **deps-dev:** Bump vue-tsc from 2.1.6 to 2.1.8 ([b3a6b5a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b3a6b5a)) - **deps-dev:** Bump vue-tsc from 2.1.8 to 2.1.10 ([73259c8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/73259c8)) - **deps-dev:** Bump vitest from 2.1.3 to 2.1.4 ([ce299ac](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ce299ac)) - **deps-dev:** Bump eslint from 9.13.0 to 9.14.0 ([2840859](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2840859)) - **deps-dev:** Bump @nuxt/eslint-config from 0.6.0 to 0.6.1 ([5f4e34c](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5f4e34c)) - **deps-dev:** Bump @types/node from 22.8.7 to 22.9.0 ([cbe9b6f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/cbe9b6f)) - **deps-dev:** Bump nuxt from 3.13.2 to 3.14.159 ([79ccffc](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/79ccffc)) - **deps-dev:** Bump @nuxt/schema from 3.13.2 to 3.14.159 ([4dbd21a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/4dbd21a)) - **deps-dev:** Bump vitest from 2.1.4 to 2.1.5 ([1bd218b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1bd218b)) - **deps-dev:** Bump @nuxt/eslint-config from 0.6.1 to 0.7.0 ([1303a9a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1303a9a)) - **deps-dev:** Bump eslint from 9.14.0 to 9.15.0 ([b71d6e8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b71d6e8)) - Install typescript-eslint with a fix ([0e6b0e5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0e6b0e5)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.5.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.5.0...v0.5.1) ### 🚀 Enhancements - Add option to prepend the global middleware ([94c3346](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/94c3346)) ### 🏡 Chore - **deps-dev:** Bump eslint from 9.11.1 to 9.12.0 ([b593bd7](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b593bd7)) - **deps-dev:** Bump @nuxt/test-utils from 3.14.2 to 3.14.3 ([e775417](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e775417)) - **deps-dev:** Bump typescript from 5.5.4 to 5.6.3 ([0963a0b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0963a0b)) - **deps-dev:** Bump @nuxt/eslint-config from 0.5.7 to 0.6.0 ([c5bc900](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c5bc900)) - Updated bug report issue template ([f8534f1](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f8534f1)) - **deps-dev:** Bump @nuxt/test-utils from 3.14.3 to 3.14.4 ([e89441f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e89441f)) - **deps-dev:** Bump vitest from 2.1.2 to 2.1.3 ([e563484](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e563484)) - **deps-dev:** Bump eslint from 9.12.0 to 9.13.0 ([412f36f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/412f36f)) ### ❤️ Contributors - Jonian Guveli - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.5.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.18...v0.5.0) ### 🚀 Enhancements - ⚠️ Use ofetch as actual dependency ([07f0213](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/07f0213)) ### 🩹 Fixes - Expect object instead of headers to log ([aeeddb8](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/aeeddb8)) #### ⚠️ Breaking Changes - ⚠️ Use ofetch as actual dependency ([07f0213](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/07f0213)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.18 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.17...v0.4.18) ### 🚀 Enhancements - Improve headers logging readability ([8160e8c](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8160e8c)) ### 🩹 Fixes - Use normalized Headers object for interceptors ([22602b6](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/22602b6)) ### 🏡 Chore - Use specific ofetch version for development ([6edf22d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6edf22d)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.17 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.16...v0.4.17) ### 🚀 Enhancements - Added headers validation and more control on config values ([c08c778](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c08c778)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.16 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.15...v0.4.16) ### 🩹 Fixes - Prevent concurrent update of request headers ([c5af530](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c5af530)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.15 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.14...v0.4.15) ### 🚀 Enhancements - Add request and response headers logging ([fed1d16](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fed1d16)) ### 🩹 Fixes - Prevent object merging with HeadersList ([393ddca](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/393ddca)) ### 🏡 Chore - **deps-dev:** Bump @nuxt/devtools from 1.5.0 to 1.5.1 ([c564ab3](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c564ab3)) - **deps-dev:** Bump @types/node from 22.5.5 to 22.7.4 ([9a2f385](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9a2f385)) - **deps-dev:** Bump eslint from 9.11.0 to 9.11.1 ([4b4ab3f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/4b4ab3f)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.14 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.13...v0.4.14) ### 🩹 Fixes - Use nuxt app context for default token storage ([1f7fdb2](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1f7fdb2)) ### 🏡 Chore - Updated issue templates ([8647cdc](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8647cdc)) - Updated bug report issue template ([fca3627](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fca3627)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.13 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.12...v0.4.13) ### 🩹 Fixes - Keep same session on SSR requests ([fc96f34](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fc96f34)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.12 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.11...v0.4.12) ### 🚀 Enhancements - Control plugin load order ([fa1e46d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fa1e46d)) - Control over initial user request ([5f3d8d4](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5f3d8d4)) ### 🏡 Chore - **deps-dev:** Bump nuxt from 3.13.0 to 3.13.1 ([33e6323](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/33e6323)) - **deps-dev:** Bump @nuxt/eslint-config from 0.5.5 to 0.5.6 ([f88ecb9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f88ecb9)) - **deps-dev:** Bump vue-tsc from 2.1.4 to 2.1.6 ([11661f9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/11661f9)) - **deps-dev:** Bump eslint from 9.9.1 to 9.10.0 ([95cd069](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/95cd069)) - **deps-dev:** Bump @nuxt/module-builder from 0.8.3 to 0.8.4 ([51fd807](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/51fd807)) - **deps-dev:** Bump @types/node from 22.5.2 to 22.5.5 ([abe3846](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/abe3846)) - **deps-dev:** Bump vitest from 2.0.5 to 2.1.1 ([bbcb244](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/bbcb244)) - Upgraded nuxt dependencies ([6b28a2a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6b28a2a)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.11 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.10...v0.4.11) ### 🏡 Chore - **deps-dev:** Bump vue-tsc from 2.1.2 to 2.1.4 ([35efc7b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/35efc7b)) - **deps-dev:** Bump @types/node from 22.5.1 to 22.5.2 ([7a7e912](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/7a7e912)) - **deps-dev:** Bump @nuxt/eslint-config from 0.5.4 to 0.5.5 ([84e836d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/84e836d)) ### 🤖 CI - Added prerelease pipeline ([deff64a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/deff64a)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.10 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.9...v0.4.10) ### 🩹 Fixes - Improved nullable schema fields ([879fdee](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/879fdee)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.9 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.8...v0.4.9) ### 🚀 Enhancements - Simplified module config ([b3bad10](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b3bad10)) ### 🩹 Fixes - Added augmented types for PageMeta ([e784f88](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e784f88)) ### 🏡 Chore - Migrated from yarn to pnpm ([bad9820](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/bad9820)) - Simplified pull request template ([8f8955b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8f8955b)) - Applied eslint formatter ([1b2670d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1b2670d)) ### 🤖 CI - Upgraded pipelines to work with pnpm ([1a041da](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1a041da)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.8 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.7...v0.4.8) ### 🩹 Fixes - Update to latest `@nuxt/module-builder` ([fdeae82](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fdeae82)) ### ❤️ Contributors - Daniel Roe ([@danielroe](http://github.com/danielroe)) ## v0.4.7 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.6...v0.4.7) ### 🩹 Fixes - Use new augmented types from nuxt 3.13 ([197f1c3](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/197f1c3)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.6 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.5...v0.4.6) ### 🩹 Fixes - Do not trim single slash in the url ([81c87d0](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/81c87d0)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.5 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.4...v0.4.5) ### 🩹 Fixes - Trim trailing slash on redirects ([d24014d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/d24014d)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.4 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.3...v0.4.4) ### 🩹 Fixes - Fallback to #app augmented type for page meta ([cbf8a6c](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/cbf8a6c)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.2...v0.4.3) ### 🩹 Fixes - Use resolved path for augmented types when using pnpm ([050c006](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/050c006)) ### 🏡 Chore - Upgraded nuxt version ([0cd2d51](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0cd2d51)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.1...v0.4.2) ### 🚀 Enhancements - Configure redirect if unauthenticated ([9574818](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9574818)) ### 🩹 Fixes - Use updated csrf token on first login ([14f5c63](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/14f5c63)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.4.0...v0.4.1) ### 🩹 Fixes - Experimental support for cloudflare workers ([9146cba](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9146cba)) ### 🏡 Chore - Updated github templates ([932fd0f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/932fd0f)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.4.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.14...v0.4.0) ### 🚀 Enhancements - ⚠️ Dropped support for excludeFromSanctum page meta ([5d09e71](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5d09e71)) - Added token storage support ([2ebcfbf](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2ebcfbf)) ### 🏡 Chore - Updated playgorund config to breeze-api ([5644023](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5644023)) - Indicate compatibility with new v4 major ([54869f7](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/54869f7)) #### ⚠️ Breaking Changes - ⚠️ Dropped support for excludeFromSanctum page meta ([5d09e71](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5d09e71)) ### ❤️ Contributors - Daniel Roe ([@danielroe](http://github.com/danielroe)) - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.14 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.13...v0.3.14) ### 🩹 Fixes - Changed path with typo ([1c9c963](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1c9c963)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.13 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.12...v0.3.13) ### 🩹 Fixes - Hardcode absolute path to node_modules for page meta ([eb0d555](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/eb0d555)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.12 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.11...v0.3.12) ### 🩹 Fixes - Adjusted path for page meta type ([c0ddc35](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c0ddc35)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.11 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.10...v0.3.11) ### 🩹 Fixes - Expose page meta augmented type ([b49fdfb](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b49fdfb)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.10 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.9...v0.3.10) ### 🩹 Fixes - Removed workspace from dependencies ([a648ae9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/a648ae9)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.9 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.8...v0.3.9) ### 🩹 Fixes - Expose type via templates ([d1ed3a4](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/d1ed3a4)) - Export public runtime config as part of module ([43fd15c](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/43fd15c)) ### 🏡 Chore - Added typing for interceptors ([8446f6a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8446f6a)) - Added transpile build option ([40fe0b2](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/40fe0b2)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.8 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.7...v0.3.8) ### 🩹 Fixes - Updated exporting nuxt schemas ([fa608f6](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/fa608f6)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.7 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.6...v0.3.7) ### 🩹 Fixes - Use common request header ([2536f21](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2536f21)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.6 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.5...v0.3.6) ### 🩹 Fixes - Export module types ([b1a513b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/b1a513b)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.5 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.4...v0.3.5) ### 🚀 Enhancements - Added support for custom interceptors ([2795998](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2795998)) ### 🩹 Fixes - Disabled typecheck in the playground ([cd77036](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/cd77036)) ### 🏡 Chore - Upgraded module build dependencies ([16cdde6](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/16cdde6)) - Extracted default module options ([e0f2c7a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e0f2c7a)) - Applied formatting to system files ([ae7918f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/ae7918f)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.4 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.3...v0.3.4) ### 🩹 Fixes - Dropped extenal cookie parser to use h3 ([1410179](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1410179)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.2...v0.3.3) ### 🩹 Fixes - Fixed cookie parses dependency ([bd6ef34](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/bd6ef34)) ### 🏡 Chore - Upgraded yarn to 4.2.2 ([007a7e4](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/007a7e4)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.1...v0.3.2) ### 🩹 Fixes - Added github token for automatic release creation ([84fac56](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/84fac56)) - Request csrf only when not set ([569dbdc](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/569dbdc)) - Added csrf cookie for secure ssr calls ([8b45e06](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8b45e06)) ### 🏡 Chore - Added debug log for user initial request ([409b3ae](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/409b3ae)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.3.0...v0.3.1) ### 🩹 Fixes - Added npm credentials config ([51d76ef](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/51d76ef)) - Added id-token permission for pipeline ([72b08ea](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/72b08ea)) - Keep config type exported in composable ([3862bfb](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/3862bfb)) ### 🏡 Chore - Upgraded setup-node to v4 ([3f6226d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/3f6226d)) - Removed temp pipeline for publishing ([3bd6027](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/3bd6027)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.3.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.2.3...v0.3.0) ### 🚀 Enhancements - Added publish workflow ([3c8baef](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/3c8baef)) - ⚠️ Added guest mode for global middleware ([50f2a10](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/50f2a10)) ### 🩹 Fixes - Include history and write permissions in release pipeline ([220e16b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/220e16b)) ### 🏡 Chore - **release:** V0.2.3 ([0397aa5](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/0397aa5)) - Dropped support of old versions ([8366255](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8366255)) #### ⚠️ Breaking Changes - ⚠️ Added guest mode for global middleware ([50f2a10](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/50f2a10)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.2.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.2.2...v0.2.3) ### 🩹 Fixes - Resolved constant visibility issue ([5cbd7a3](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/5cbd7a3)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.2.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.2.0...v0.2.2) ### 🚀 Enhancements - Added logger support ([9a62a8f](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/9a62a8f)) - Added logs into plugin ([79e2558](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/79e2558)) ### 🩹 Fixes - Remove git push with tags ([03b28d4](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/03b28d4)) - Prevent infinite redirect for guests ([20e7cc9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/20e7cc9)) - Removed redundant empty line ([6426309](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6426309)) - Reset user only when it expires ([d314b86](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/d314b86)) - Request identity once on plugin init ([c88fbdf](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/c88fbdf)) - Push main branch after bumping release version ([cc76b0e](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/cc76b0e)) ### 📖 Documentation - Use new `nuxi module add` command in installation ([d74c007](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/d74c007)) - Update second step ([93e365d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/93e365d)) ### 🏡 Chore - **release:** V0.2.1 ([cf80175](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/cf80175)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) - Daniel Roe ([@danielroe](http://github.com/danielroe)) ## v0.2.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.2.0...v0.2.1) ### 🩹 Fixes - Remove git push with tags ([03b28d4](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/03b28d4)) - Prevent infinite redirect for guests ([20e7cc9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/20e7cc9)) ### 📖 Documentation - Use new `nuxi module add` command in installation ([d74c007](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/d74c007)) - Update second step ([93e365d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/93e365d)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) - Daniel Roe ([@danielroe](http://github.com/danielroe)) ## v0.2.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.1.2...v0.2.0) ### 🚀 Enhancements - Add Origin header to the request ([6387379](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6387379)) - Added separate error page ([79ce7b2](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/79ce7b2)) - Implemented global middleware ([6322fd3](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/6322fd3)) ### 🩹 Fixes - Opt in to `import.meta.*` properties ([99e98c9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/99e98c9)) - Prevent redirects to the same page ([01daa22](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/01daa22)) - Prevent redirect on 401 response from login page ([380d4a6](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/380d4a6)) - Removed mistaken command from contributing rules ([f4fbf82](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f4fbf82)) - Adjusted tsc directories to check ([de23cbb](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/de23cbb)) ### 📖 Documentation - Added separate gitbook as module docs ([f7c4edc](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f7c4edc)) - Added toc to readme ([497386b](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/497386b)) ### 🏡 Chore - **release:** V0.1.2 ([f23e51a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/f23e51a)) - Code style improvements ([32b0a64](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/32b0a64)) - Simplified middleware checks ([1c186b9](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1c186b9)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) - Daniel Roe ([@danielroe](http://github.com/danielroe)) ## v0.1.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.1.1...v0.1.2) ### 🩹 Fixes - Added required config to fixture env ([09b621d](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/09b621d)) - Updated nuxt dependencies and fixed test behavior ([e06cb36](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/e06cb36)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.1.1 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.1.0...v0.1.1) ### 🩹 Fixes - Dumped compatibility version to 3.9 ([1268ffc](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1268ffc)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.1.0 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.17...v0.1.0) ### 🚀 Enhancements - Upgraded nuxt dependencies ([2c82ae6](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/2c82ae6)) - Handle cookies in read-only mode ([632abab](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/632abab)) ### 🩹 Fixes - Remove redundant config from playground ([8288cc0](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8288cc0)) - Revert nuxt 3.10 to 3.9 because of bugs ([1eb3f0a](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/1eb3f0a)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.0.17 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.16...v0.0.17) ## v0.0.16 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.15...v0.0.16) ### 🚀 Enhancements - **origin:** Set to optional ([dfe0805](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/dfe0805)) - **origin:** Fix indent ([a50d2c7](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/a50d2c7)) ### ❤️ Contributors - Ugo Mignon ## v0.0.14 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.13...v0.0.14) ### 🏡 Chore - Test bundler module resolution ([4dc2cde](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/4dc2cde)) ### ❤️ Contributors - Daniel Roe ## v0.0.13 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/list...v0.0.13) ### 🏡 Chore - **release:** V0.0.12 ([8230041](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/8230041)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.0.12 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/list...v0.0.12) ## v0.0.11 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.10...v0.0.11) ## v0.0.10 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.9...v0.0.10) ## v0.0.9 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.8...v0.0.9) ## v0.0.8 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.7...v0.0.8) ## v0.0.7 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.6...v0.0.7) ## v0.0.6 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.5...v0.0.6) ### 🏡 Chore - **release:** V0.0.5 ([d52546e](https://github.com/manchenkoff/nuxt-auth-sanctum/commit/d52546e)) ### ❤️ Contributors - Manchenkoff ([@manchenkoff](http://github.com/manchenkoff)) ## v0.0.5 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.4...v0.0.5) ## v0.0.4 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.3...v0.0.4) ## v0.0.3 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.2...v0.0.3) ## v0.0.2 [compare changes](https://github.com/manchenkoff/nuxt-auth-sanctum/compare/v0.0.1...v0.0.2) ## v0.0.1 - Initial release - Added first implementation of the module - Added readme - Published NPM package ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: - Demonstrating empathy and kindness toward other people - Being respectful of differing opinions, viewpoints, and experiences - Giving and gracefully accepting constructive feedback - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: - The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without their explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at artem@manchenkoff.me. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # How to contribute Please follow the guidelines below if you want to contribute to the project. If you are working on a new feature, please create an issue on GitHub first. This will help us to understand what you are working on and to avoid duplication of work. # Development environment You can clone the repo by running the following command: ```bash git clone git@github.com:manchenkoff/nuxt-auth-sanctum.git ``` Then you should create a new branch with the following name convention: ```bash git checkout -b XXX-feature-name ``` Where `XXX` is the issue number on the GitHub. ## Install dependencies To setup the development environment, you should install the dependencies first. You can do this by running the following command: ```bash pnpm install ``` Then you can start dev server to see the playground app: ```bash pnpm dev ``` Or if you want to build the project, you can run one of the following commands: ```bash # Generate type stubs pnpm dev:prepare # Build the playground pnpm dev:build ``` # Testing process To test playground app you need to have a Laravel API running with Sanctum package installed. if there are tests for the feature you are working on, you can run them by executing one of the following commands: ```bash # Run Vitest pnpm test # Run Vitest in watch mode pnpm test:watch ``` # Testing package locally If you want to test the package before publishing it to `npm`, you can create an archive and install it as a dependency in your project. To do this, run the following command: ```bash pnpm rc ``` This will create a `.tgz` file in the `dist` directory. You can then reference it in your project: ```json { "devDependencies": { "nuxt-auth-sanctum": "file:/dist/nuxt-auth-sanctum-0.0.0.tgz" } } ``` Then you can install the package by running: ```bash pnpm install ``` # Code Style and Standards This project uses ESLint to enforce code style and standards. Please make sure to run the following commands before creating a pull request: ```bash # Run ESLint pnpm lint # Run Nuxt type check pnpm test:types # Run Vitest pnpm test ``` # Releasing Once all the changes are merged into main branch, run the following command to release a new version: ```bash pnpm release ``` # Code of Conduct All contributors are expected to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). Please read it. # Get in touch If you have any questions or need help, feel free to reach out via artem@manchenkoff.me or by opening a new issue on Github. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 Artem Manchenkov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Nuxt Auth Sanctum [![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![License][license-src]][license-href] [![Nuxt][nuxt-src]][nuxt-href] This module provides a simple way to use Laravel Sanctum with Nuxt by leveraging cookies-based authentication. SSR-ready! - [Documentation](https://sanctum.manchenkoff.me) - [Features](#features) - [Quick Setup](#quick-setup) ## Features This module includes a range of features designed to streamline authentication: - `useSanctumAuth` composable for easy access to the current user and authentication methods - `useSanctumFetch` and `useLazySanctumFetch` to load data from your API - Automated `CSRF` token header and cookie management - Automated `Bearer` token header management - Both `CSR` and `SSR` modes support - Pre-configured middleware for pages that require authentication - Cast current user information to any class you want - Custom `request` and `response` interceptors - Subscribe to `sanctum:*` hooks to react as you want - Compatible with default Nuxt `ofetch` client - TypeScript support - ... and more, check the docs! **Note:** Before using this module, please make sure that you have already configured Laravel Sanctum on your backend. You can find more information about Laravel Sanctum [here](https://laravel.com/docs/10.x/sanctum#spa-authentication). Complete documentation - [Nuxt Auth Sanctum docs](https://sanctum.manchenkoff.me) ## Quick Setup 1. Add `nuxt-auth-sanctum` dependency to your project ```bash npx nuxi@latest module add nuxt-auth-sanctum ``` 2. Add any required configuration in your `nuxt.config.ts` file ```js export default defineNuxtConfig({ modules: ["nuxt-auth-sanctum"], sanctum: { baseUrl: "http://localhost:80", // Laravel API }, }); ``` That's it! You can now use Nuxt Auth Sanctum in your Nuxt app ✨ For more details, check the documentation [here](https://sanctum.manchenkoff.me) [npm-version-src]: https://img.shields.io/npm/v/nuxt-auth-sanctum/latest.svg?style=flat&colorA=18181B&colorB=28CF8D [npm-version-href]: https://npmjs.com/package/nuxt-auth-sanctum [npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-auth-sanctum.svg?style=flat&colorA=18181B&colorB=28CF8D [npm-downloads-href]: https://npm.chart.dev/nuxt-auth-sanctum [license-src]: https://img.shields.io/npm/l/nuxt-auth-sanctum.svg?style=flat&colorA=18181B&colorB=28CF8D [license-href]: https://npmjs.com/package/nuxt-auth-sanctum [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js [nuxt-href]: https://nuxt.com ### Powered by [![JetBrains logo.](https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg)](https://jb.gg/OpenSource) ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions | Version | Supported | | -------- | ------------------ | | >= 0.3.0 | :white_check_mark: | | <= 0.2.0 | :x: | ## Reporting a Vulnerability If you find a vulnerability, please get in touch with me via artem@manchenkoff.me or by opening a new issue in the repository. Usually, I respond in a few days but sometimes it might take a bit more. ================================================ FILE: TROUBLESHOOTING.md ================================================ # Troubleshooting Guide If you experience any issues while using this module, please check this link - [Troubleshooting](https://sanctum.manchenkoff.me/advanced/troubleshooting). ================================================ FILE: docs/.env.example ================================================ # Public URL, used for OG Image when running nuxt generate NUXT_PUBLIC_SITE_URL=https://sanctum.manchenkoff.me ================================================ FILE: docs/.gitignore ================================================ # Nuxt dev/build outputs .output .data .nuxt .nitro .cache dist # Node dependencies node_modules # Logs logs *.log # Misc .DS_Store .fleet .idea # Local env files .env .env.* !.env.example # VSC .history ================================================ FILE: docs/.npmrc ================================================ shamefully-hoist=true ================================================ FILE: docs/README.md ================================================ # Nuxt - Laravel Sanctum Docs This directory contains Nuxt Content project to deploy module documentation to Github Pages. ## Setup Make sure to install the dependencies: ```bash pnpm install ``` ## Development Server Start the development server on `http://localhost:3000`: ```bash pnpm dev ``` ## Production Build the application for production: ```bash pnpm build ``` Or generate all pages for static hosting via: ```bash pnpm generate ``` Locally preview production build: ```bash pnpm preview ``` ## Validation To check code quality, run the following command: ```bash pnpm validate ``` or separately: ```bash pnpm lint pnpm typecheck ``` ================================================ FILE: docs/app/app.config.ts ================================================ const siteName = 'Nuxt - Laravel Sanctum' export default defineAppConfig({ ui: { colors: { primary: 'red', neutral: 'zinc' }, footer: { slots: { root: 'border-t border-default', left: 'text-sm text-muted' } } }, seo: { siteName: siteName }, header: { title: siteName, to: '/', logo: { alt: 'Laravel Sanctum', light: 'logo.svg', dark: 'logo.svg' }, search: true, colorMode: true, links: [ { 'icon': 'i-simple-icons-github', 'to': 'https://github.com/manchenkoff/nuxt-auth-sanctum', 'target': '_blank', 'aria-label': 'GitHub' }, { 'icon': 'i-simple-icons-nuxt', 'to': 'https://nuxt.com/modules/nuxt-auth-sanctum', 'target': '_blank', 'aria-label': 'Nuxt Module' } ] }, footer: { credits: `Artem Manchenkov © ${new Date().getFullYear()}`, colorMode: false, links: [ { 'icon': 'i-simple-icons-github', 'to': 'https://github.com/manchenkoff', 'target': '_blank', 'aria-label': 'manchenkoff on GitHub' }, { 'icon': 'i-simple-icons-twitter', 'to': 'https://twitter.com/amanchenkov', 'target': '_blank', 'aria-label': 'manchenkoff on X' }, { 'icon': 'i-simple-icons-facebook', 'to': 'https://fb.com/manchenkoff', 'target': '_blank', 'aria-label': 'manchenkoff on Facebook' }, { 'icon': 'i-simple-icons-linkedin', 'to': 'https://linkedin.com/in/manchenkoff', 'target': '_blank', 'aria-label': 'manchenkoff on LinkedIn' }, { 'icon': 'i-simple-icons-instagram', 'to': 'https://instagram.com/manchenkof', 'target': '_blank', 'aria-label': 'manchenkoff on Instagram' }, { 'icon': 'i-simple-icons-threads', 'to': 'https://threads.net/@manchenkof', 'target': '_blank', 'aria-label': 'manchenkoff on Threads' }, { 'icon': 'i-simple-icons-youtube', 'to': 'https://youtube.com/@manchenkoff', 'target': '_blank', 'aria-label': 'manchenkoff on YouTube' }, { 'icon': 'i-simple-icons-medium', 'to': 'https://manchenkoff.medium.com/', 'target': '_blank', 'aria-label': 'manchenkoff on Medium' }, { 'icon': 'i-simple-icons-telegram', 'to': 'https://t.me/manchenkoff', 'target': '_blank', 'aria-label': 'manchenkoff on Telegram' }, { 'icon': 'i-simple-icons-bluesky', 'to': 'https://bsky.app/profile/manchenkoff.bsky.social', 'target': '_blank', 'aria-label': 'manchenkoff on Bluesky' } ] }, toc: { title: 'Table of Contents', bottom: { title: 'Ready to contribute?', edit: 'https://github.com/manchenkoff/nuxt-auth-sanctum/edit/main/docs/content', links: [ { icon: 'i-lucide-star', label: 'Star on GitHub', to: 'https://github.com/manchenkoff/nuxt-auth-sanctum', target: '_blank' }, { icon: 'i-lucide-git-pull-request-create', label: 'Suggest a feature', to: 'https://github.com/manchenkoff/nuxt-auth-sanctum/issues/new?template=feature_request.md', target: '_blank' }, { icon: 'i-simple-icons-github', label: 'Support project', to: 'https://github.com/sponsors/manchenkoff?o=esb', target: '_blank' }, { icon: 'i-simple-icons-buymeacoffee', label: 'Buy me a coffee', to: 'https://buymeacoffee.com/manchenkoff', target: '_blank' } ] } } }) ================================================ FILE: docs/app/app.vue ================================================ ================================================ FILE: docs/app/assets/css/main.css ================================================ @import "tailwindcss"; @import "@nuxt/ui"; @source "../../../content/**/*"; @theme static { --container-8xl: 90rem; --font-sans: 'Public Sans', sans-serif; } :root { --ui-container: var(--container-8xl); } ================================================ FILE: docs/app/components/AppFooter.vue ================================================ ================================================ FILE: docs/app/components/AppHeader.vue ================================================ ================================================ FILE: docs/app/components/AppLogo.vue ================================================ ================================================ FILE: docs/app/components/OgImage/OgImageDocs.takumi.vue ================================================ ================================================ FILE: docs/app/components/content/HeroBackground.vue ================================================ ================================================ FILE: docs/app/components/content/StarsBackground.vue ================================================ ================================================ FILE: docs/app/error.vue ================================================ ================================================ FILE: docs/app/layouts/docs.vue ================================================ ================================================ FILE: docs/app/pages/[...slug].vue ================================================ ================================================ FILE: docs/app/pages/index.vue ================================================ ================================================ FILE: docs/content/1.getting-started/.navigation.yml ================================================ title: Getting Started icon: false ================================================ FILE: docs/content/1.getting-started/1.index.md ================================================ --- title: Introduction description: Welcome to Nuxt Laravel Sanctum module documentation navigation: icon: i-simple-icons-laravel --- This module provides a simple way to use Laravel Sanctum with Nuxt. SSR-ready! ## Key Features This module includes a range of features designed to streamline authentication: - `useSanctumAuth` composable for easy access to the current user and authentication methods - `useSanctumFetch` and `useLazySanctumFetch` to load data from your API - Automated `CSRF` token header and cookie management - Automated `Bearer` token header management - Both `CSR` and `SSR` modes support - Pre-configured middleware for pages that require authentication - Cast current user information to any class you want - Custom `request` and `response` interceptors - Subscribe to `sanctum:*` hooks to react as you want - Compatible with default Nuxt `ofetch` client - TypeScript support - ... and more, check the docs! ::warning --- target: _blank to: https://laravel.com/docs/10.x/sanctum#spa-authentication --- **Note**: Before using this module, please ensure you have configured Laravel Sanctum on your backend. You can find more information about Laravel Sanctum here. :: We recommend looking at our [breeze-nuxt](https://github.com/manchenkoff/breeze-nuxt) template that works flawlessly with [breeze-api](https://github.com/manchenkoff/breeze-api) Laravel application with preconfigured Sanctum and Echo modules. ## Ecosystem This project is a part of Nuxt Laravel modules ecosystem which you may find useful: ::card-group :::card --- icon: i-lucide-lock target: _blank title: Sanctum to: https://github.com/manchenkoff/nuxt-auth-sanctum --- Module for Sanctum authentication ::: :::card --- icon: i-lucide-radio target: _blank title: Echo to: https://github.com/manchenkoff/nuxt-laravel-echo --- Module for Echo broadcasting ::: :::card --- icon: i-lucide-badge-check target: _blank title: Precognition to: https://github.com/manchenkoff/nuxt-sanctum-precognition --- Module for Precognition form validation and Nuxt UI support, based on Sanctum ::: :::card --- icon: i-simple-icons-nuxt target: _blank title: Breeze Nuxt to: https://github.com/manchenkoff/breeze-nuxt --- Nuxt application starter with configured modules for Laravel ::: :::card --- icon: i-simple-icons-laravel target: _blank title: Breeze API to: https://github.com/manchenkoff/breeze-api --- Laravel API application starter with preconfigured Sanctum, Echo and Precognition ::: :: ## Support If you like this module, please support the project to help me maintain and improve it! Buy Me A Coffee ================================================ FILE: docs/content/1.getting-started/2.installation.md ================================================ --- title: Installation description: How to add nuxt-auth-sanctum to your Nuxt application! navigation: icon: i-lucide-download --- ## Quick Start You can use the following command to install the module and automatically register it in your `nuxt.config.ts` modules section ```bash [Terminal] npx nuxi@latest module add nuxt-auth-sanctum ``` or manually install a dependency via: ```bash [Terminal] pnpm add nuxt-auth-sanctum ``` and register the module in your `nuxt.config.ts`: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: [ // other modules 'nuxt-auth-sanctum' ], sanctum: {}, }) ``` ## Configuration Once you have the module installed and registered, provide the configuration in `nuxt.config.ts` according to your setup. ```typescript [nuxt.config.ts] export default defineNuxtConfig({ //... other parts of the config // nuxt-auth-sanctum options (also configurable via environment variables) sanctum: { baseUrl: 'http://localhost:80', // Laravel API } }) ``` That's it! You can now use Nuxt Auth Sanctum in your Nuxt app ✨ ================================================ FILE: docs/content/2.usage/1.configuration.md ================================================ --- title: Configuration description: How to configure nuxt-auth-sanctum for Nuxt navigation: icon: i-lucide-cog --- ## Initial setup The only required configuration option is `baseUrl` which will be used for API calls to your Laravel API, so you can start using the module with the following definition: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: ['nuxt-auth-sanctum'], sanctum: { baseUrl: 'http://localhost:80', // Laravel API }, }) ``` ## Available options For any additional configurations, you can adjust the next list of available parameters: | Parameter | Description | Default | | --------- | ----------- | ------- | | `baseUrl` | The base URL of the Laravel API | `undefined` | | `mode` | Authentication mode to work with Laravel API. Supported values - `cookie`, `token`. | `cookie` | | `origin` | The URL of the current application to use in Referrer header | `useRequestUrl().origin` | | `userStateKey` | The key to use to store the user identity in the `useState` variable. | `sanctum.user.identity` | | `redirectIfAuthenticated` | Determine whether to redirect the user if it is already authenticated on a login attempt. | `false` | | `redirectIfUnauthenticated` | Determine whether to redirect when the user got unauthenticated on any API request. | `false` | | `endpoints.csrf` | The endpoint to request a new CSRF token | `/sanctum/csrf-cookie` | | `endpoints.login` | The endpoint to send user credentials to authenticate | `/login` | | `endpoints.logout` | The endpoint to destroy current user session | `/logout` | | `endpoints.user` | The endpoint to fetch current user data | `/api/user` | | `csrf.cookie` | Name of the CSRF cookie to extract from server response | `XSRF-TOKEN` | | `csrf.header` | Name of the CSRF header to pass from client to server | `X-XSRF-TOKEN` | | `client.retry` | The number of times to retry a request when it fails | `false` | | `client.initialRequest` | Determines whether to request the user identity on plugin initialization | `true` | | `redirect.keepRequestedRoute` | Determines whether to keep the requested route when redirecting after login | `false` | | `redirect.onLogin` | Route to redirect to when user is authenticated. If set to false, do nothing | `/` | | `redirect.onLogout` | Route to redirect to when user is not authenticated. If set to false, do nothing | `/` | | `redirect.onAuthOnly` | Route to redirect to when user has to be authenticated. If set to false, do nothing | `/login` | | `redirect.onGuestOnly` | Route to redirect to when user has to be a guest. If set to false, do nothing | `/` | | `globalMiddleware.enabled` | Determines whether the global middleware is enabled | `false` | | `globalMiddleware.prepend` | Determines whether the global middleware is prepended to the list of middlewares | `false` | | `globalMiddleware.allow404WithoutAuth` | Determines whether to allow 404 page without authentication | `true` | | `logLevel` | The level to use for the logger. More details [here](/advanced/logging). | `3` | | `appendPlugin` | Determines whether to append the plugin to the Nuxt application. More details [here](https://nuxt.com/docs/api/kit/plugins#options). | `false` | | `serverProxy.enabled` | Determines whether the server side proxy is enabled. Available on server-side only. | `false` | | `serverProxy.route` | Nuxt server route to catch all requests. This route will receive any nested path as well. Available on server-side only. | `/api/sanctum` | | `serverProxy.baseUrl` | The base URL of the Laravel API. Available on server-side only. | `http://localhost:80` | For more details, please check the source code - [options.ts](https://github.com/manchenkoff/nuxt-auth-sanctum/blob/main/src/runtime/types/options.ts). ## Overrides You can override any of these options in the `nuxt.config.ts` file: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: ['nuxt-auth-sanctum'], sanctum: { baseUrl: 'http://localhost:80', // Your Laravel API redirect: { onLogin: '/dashboard', // Custom route after successful login }, }, }) ``` ## RuntimeConfig Module configuration is exposed to `runtimeConfig` property of your Nuxt app, so you can override either in sanctum module config or `runtimeConfig.public.sanctum` property. ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: ['nuxt-auth-sanctum'], runtimeConfig: { public: { sanctum: { baseUrl: 'http://localhost:80', }, }, }, }) ``` ## Server vs Client Configuration The module supports different configurations for server-side (SSR) and client-side (CSR) contexts. ### Configuration Priority | Context | Priority (highest to lowest) | |---------|------------------------------| | Server | `runtimeConfig.sanctum` → `runtimeConfig.public.sanctum` → module defaults | | Client | `runtimeConfig.public.sanctum` → module defaults | ### Examples **Shared config** (same for both server and client): ```typescript [nuxt.config.ts] runtimeConfig: { public: { sanctum: { baseUrl: 'http://localhost:80', }, }, } ``` **Different config** (server vs client): ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: ['nuxt-auth-sanctum'], // Default values for both server and client sanctum: { baseUrl: 'http://localhost:80', logLevel: 3, }, // Server-specific overrides runtimeConfig: { sanctum: { baseUrl: 'http://laravel:80', // Docker internal URL logLevel: 4, // Verbose server logs }, // Client-specific overrides public: { sanctum: { baseUrl: 'https://myapp.com', // Public TLD logLevel: 2, // Minimal client logs } } }, }) ``` ### Server-Only Options The following options are only available on the server-side: - `serverProxy.enabled` - `serverProxy.route` - `serverProxy.baseUrl` ## Environment variables It is possible to override options via environment variables too. It might be useful when you want to use `.env` file to provide baseUrl for Laravel API. ::warning If you are using SSR (Server-Side Rendering) and relying *entirely* on `.env` files rather than hardcoding the `baseUrl` in your `nuxt.config.ts`, you **must** provide both the public and private environment variables. Otherwise, the server-side fetch will not see the public variable and the page will hang indefinitely with an infinite loop of SSR requests. :: Here is what it should look like in your `.env` file: ```env [.env] # Used by the browser (CSR) NUXT_PUBLIC_SANCTUM_BASE_URL='http://localhost:8000' # Used by the Nuxt server (SSR) NUXT_SANCTUM_BASE_URL='http://localhost:8000' ``` ::warning The `origin` option requires a static default in `nuxt.config.ts` for environment variables to work. Unlike other options, Nuxt ignores env var overrides for keys that are `undefined` by default. ```typescript sanctum: { origin: 'http://localhost:3000', // Set static default first } ``` Then in your `.env`: ```env # For client-side (CSR) NUXT_PUBLIC_SANCTUM_ORIGIN=https://your-domain.com # For server-side (SSR) NUXT_SANCTUM_ORIGIN=https://your-domain.com ``` Note: `NUXT_PUBLIC_SANCTUM_ORIGIN` only affects client-side, while `NUXT_SANCTUM_ORIGIN` only affects server-side. :: ## Configuration example Here is an example of a full module configuration ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: ['nuxt-auth-sanctum'], sanctum: { mode: 'cookie', userStateKey: 'sanctum.user.identity', redirectIfAuthenticated: false, redirectIfUnauthenticated: false, endpoints: { csrf: '/sanctum/csrf-cookie', login: '/login', logout: '/logout', user: '/api/user', }, csrf: { cookie: 'XSRF-TOKEN', header: 'X-XSRF-TOKEN', }, client: { retry: false, initialRequest: true, }, redirect: { keepRequestedRoute: false, onLogin: '/', onLogout: '/', onAuthOnly: '/login', onGuestOnly: '/', }, globalMiddleware: { enabled: false, allow404WithoutAuth: true, }, logLevel: 3, appendPlugin: false, } }) ``` ================================================ FILE: docs/content/2.usage/2.cookie.md ================================================ --- title: Cookie Authentication description: Set up cookie CSRF authentication for Sanctum navigation: icon: i-lucide-cookie --- ## Usage By default, the module provides configuration to integrate seamlessly with Laravel Sanctum authentication based on the XSRF token. To explicitly set this authentication mode, update `sanctum.mode` configuration property to `cookie`. You can check the official Laravel documentation here - [SPA Authentication](https://laravel.com/docs/12.x/sanctum#spa-authentication). ::warning Nuxt and Laravel applications must share the same top-level domain. For instance: - Nuxt application - `domain.com` - Laravel application - `api.domain.com` :: ## How it works First, you need to authenticate a user by submitting credentials to `endpoints.login` endpoint: ```typescript const { login } = useSanctumAuth() const credentials = { email: "john@doe.com", password: "password", remember: true, } await login(credentials) ``` The client will be automatically redirected to `redirect.onLogin` route of your application. Once the module has an authentication state, it will take care of requesting a CSRF cookie from the API and passing it as an XSRF header to each subsequent request as well as passing all other headers and cookies from CSR to SSR requests. You can also [extend default interceptors](/advanced/interceptors) and add your information into headers or cookie collections. To check other available methods, please refer to the composables section. ## Laravel configuration Your Laravel API should be configured properly to support Nuxt domain and share cookies: - The Nuxt application domain should be registered in `stateful` domain list (`SANCTUM_STATEFUL_DOMAINS`) - The Nuxt application domain should be registered in `config/cors.php` in `allowed_origins` domain list - Also `config/cors.php` configuration should have `support_credentials=true` - Sanctum `statefulApi` middleware should be enabled - The top-level domain should be used for the session (`SESSION_DOMAIN=.domain.com`), or `localhost` during development (without port) If you notice incorrect behavior of the module or authentication flow, feel free to [raise an issue](https://github.com/manchenkoff/nuxt-auth-sanctum/issues/new/choose)! ================================================ FILE: docs/content/2.usage/3.token.md ================================================ --- title: Token Authentication description: Set up cookie Bearer token authentication for Sanctum navigation: icon: i-lucide-shield --- ## Usage ::caution Beware, that token-based authentication is not recommended for SPA applications. :: Sometimes, token authentication might be useful when you cannot host your application on the same TLD or have a mobile or desktop application built with Nuxt (e.g. based on Capacitor). To explicitly set this authentication mode, update `sanctum.mode` configuration property to `token`. You can check the official Laravel documentation here - [API Token Authentication](https://laravel.com/docs/12.x/sanctum#api-token-authentication). ## How it works First, you need to authenticate a user by submitting credentials to `endpoints.login` endpoint: ```typescript const { login } = useSanctumAuth() const credentials = { email: "john@doe.com", password: "password", remember: true, } await login(credentials) ``` The client will be automatically redirected to `redirect.onLogin` route of your application. To check other available methods, please refer to the **composables** section. The module expects a plain token value in the response from the API that can be stored in cookies to be included in all subsequent requests as `Authorization` header. You can also implement your [own token storage](https://sanctum.manchenkoff.me/advanced/token-storage) if cookies are not supported, for example - *Capacitor, Ionic, LocalStorage, etc*. ## Laravel configuration Your API should have at least two endpoints for login and logout which are included in `api.php` routes, so make sure that you do not use the same endpoints as for cookie-based authentication (`web.php` routes) to avoid **CSRF token mismatch** errors. ```php [routes/api.php] post('/login', [TokenAuthenticationController::class, 'store']); Route::middleware(['auth:sanctum'])->post('/logout', [TokenAuthenticationController::class, 'destroy']); ``` ::warning Keep in mind, that the domain where API requests are coming from should not be included in `SANCTUM_STATEFUL_DOMAINS` variable, otherwise you will get a **CSRF mismatch error**. :: The login endpoint must return a JSON response that contains `token` key like this ```json { "token": "" } ``` Here you can find an example from official documentation - [Issue API Token](https://laravel.com/docs/12.x/sanctum#issuing-api-tokens). The logout endpoint should revoke the current client token to avoid inconsistencies with your Nuxt application state, please check official documentation - [Revoke API Tokens](https://laravel.com/docs/12.x/sanctum#revoking-tokens). ::tip You can also try our API template with the already implemented authentication logic for both cookie and token approach - [breeze-nuxt](/advanced/breeze-nuxt-template). :: ## Custom token storage Default token storage uses cookies to keep the API Authentication token and automatically load it for both CSR and SSR requests. However, you are free to define custom storage in your `app.config.ts` by implementing an interface. Check this section for more details - [Token storage](/advanced/token-storage). ================================================ FILE: docs/content/2.usage/4.proxy.md ================================================ --- title: Server Proxy description: How to use your Nuxt app as a proxy for Sanctum API requests navigation: icon: i-lucide-waypoints --- ## Usage When using **SSR**, it's often convenient to serve all operations under a single domain. You can achieve this by enabling the **server proxy catch-all** feature, which forwards client requests to your Laravel API while preserving cookies and headers. To enable it, update your `nuxt.config.ts`: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: ['nuxt-auth-sanctum'], ssr: true, sanctum: { baseUrl: '/api/sanctum', serverProxy: { enabled: true, route: '/api/sanctum', baseUrl: 'http://api.frontend.dev', }, }, }) ``` Once `serverProxy.enabled` is set to `true`, Nuxt adds a server route at: `http://frontend.dev/api/sanctum` which is defined as `serverProxy.route` parameter. Note that `sanctum.baseUrl` is now `/api/sanctum` (a local path), while `serverProxy.baseUrl` points to your Laravel backend. This setup tells Nuxt where to forward requests internally. You can test the proxy using any helper like `useSanctumFetch`: ```typescript // Request URL = http://frontend.dev/api/sanctum/user/profile // Actual URL = http://api.frontend.dev/user/profile const { data } = await useSanctumFetch('/user/profile') ``` The catch-all route (`/api/sanctum`) is stripped from the final proxied URL. If needed, you can customise this behaviour by modifying `serverProxy.baseUrl`. ================================================ FILE: docs/content/3.composables/1.useSanctumAuth.md ================================================ --- title: useSanctumAuth description: How to use useSanctumAuth composable to work with state navigation: icon: i-lucide-parentheses --- ## Usage Composable provides 2 computed properties and 4 methods: - `user` - currently authenticated user (basically the same as `useSanctumUser`) - `isAuthenticated` - a boolean flag indicating whether the user is authenticated or not - `login` - method for logging in the user - `logout` - method for logging out the user - `refreshIdentity` - method for manually re-fetching current authenticated user data To authenticate a user you should pass the credentials payload as an argument to the `login` method. The payload should contain all fields required by your Laravel Sanctum backend. ```typescript const { login } = useSanctumAuth(); const userCredentials = { email: "user@mail.com", password: "123123", } await login(userCredentials) ``` If the login operation was successful, the `user` property will be updated with the current user information returned by the Laravel API. If you do not want to update the `user` property automatically (e.g. *for 2FA authentication*), you can disable identity fetching by passing optional argument to `login` method: ```typescript // user identity will not be loaded after successful response await login(userCredentials, false) ``` By default, methods will use the following Laravel endpoints: - `/login` to authenticate the user - `/logout` to log out the user - `/api/user` to get the current user information - `/sanctum/csrf-cookie` to get the `CSRF` token cookie To change the default endpoints, please check the [Configuration](/usage/configuration) section. ### Additional `fetch` options If you want to pass additional header or change HTTP method for either `login` or `logout` calls, you can pass optional `options: SanctumFetchOptions` argument. For example, to log out the user with `DELETE` method instead of default `POST`: ```typescript const { logout } = useSanctumAuth() await logout({ method: "DELETE" }) ``` Use the same approach when you need to pass additional params to `login` call: ```typescript const { login } = useSanctumAuth() const userCredentials = { email: "user@mail.com", password: "123123", } await login( userCredentials, false, { headers: { "X-Custom-Header": "header_value" } } ) ``` ================================================ FILE: docs/content/3.composables/2.useSanctumUser.md ================================================ --- title: useSanctumUser description: How to use useSanctumUser composable to work with authenticated user navigation: icon: i-lucide-parentheses --- ## Usage This composable provides access to the current authenticated user. It supports generic types, so you can get the user as any class you want. ```typescript interface MyCustomUser { id: number; login: string; custom_metadata: { group: string; role: string; }; } const user = useSanctumUser(); ``` If there is no authenticated user, the composable will return `null`. ================================================ FILE: docs/content/3.composables/3.useSanctumClient.md ================================================ --- title: useSanctumClient description: How to use useSanctumClient to work with preconfigured fetch client navigation: icon: i-lucide-parentheses --- ## Usage All previous composables work on top of the `ofetch` client which can be used in your application as well. The client is pre-configured with `CSRF` token header and cookie management. All requests will be sent to the `baseUrl` specified in the [Configuration](/usage/configuration) section. ```typescript const client = useSanctumClient(); const { data, status, error, refresh } = await useAsyncData('users', () => client('/api/users') ); ``` Since client implements `$Fetch` interface, you can use it as a regular `ofetch` client. Check examples in the ofetch [documentation](https://github.com/unjs/ofetch?tab=readme-ov-file#%EF%B8%8F-create-fetch-with-default-options). ================================================ FILE: docs/content/3.composables/4.useSanctumFetch.md ================================================ --- title: useSanctumFetch description: How to use useSanctumFetch to fetch data from Laravel API navigation: icon: i-lucide-parentheses --- ## Usage Besides `useSanctumClient` you can directly send a request by using a module-specific version of fetch composable - `useSanctumFetch`. This composable implements an API-specific `useFetch`, so you can check more details [here](https://nuxt.com/docs/api/composables/use-fetch). Composable accepts 2 arguments: - `url`: target endpoint to call (type: `MaybeRefOrGetter`) - `options`: fetch options for client (type: `UseFetchOptions`) ```typescript const { data, status, error, refresh } = await useSanctumFetch("/api/users") // or const { data, status, error, refresh } = await useSanctumFetch("/api/users", { pick: ["id"], method: "GET", query: { is_active: true, }, }) ``` You can also use type casting to work with the response as an interface: ```typescript interface MyResponse { name: string } const { data } = await useSanctumFetch("/api/endpoint") const name = data.value.name // augmented by MyResponse interface ``` ================================================ FILE: docs/content/3.composables/5.useLazySanctumFetch.md ================================================ --- title: useLazySanctumFetch description: How to use useLazySanctumFetch to fetch data from Laravel API navigation: icon: i-lucide-parentheses --- ## Usage Besides `useSanctumClient` you can directly send a request by using a module-specific version of fetch composable - `useLazySanctumFetch`. This composable implements an API-specific `useLazyFetch`, so you can check more details [here](https://nuxt.com/docs/api/composables/use-fetch). Composable accepts 2 arguments: - `url`: target endpoint to call (type: `MaybeRefOrGetter`) - `options`: fetch options for client (type: `UseFetchOptions`) ```typescript const { data, status, error, refresh } = await useLazySanctumFetch("/api/users") // or const { data, status, error, refresh } = await useLazySanctumFetch( "/api/users", { method: "GET", query: { page: 1 }, default() { return { data: [], meta: { total: 0, per_page: 0, }, } }, }, ) ``` You can also use type casting to work with the response as an interface: ```typescript interface MyResponse { name: string } const { data } = await useLazySanctumFetch("/api/endpoint") const name = data.value.name // augmented by MyResponse interface ``` ================================================ FILE: docs/content/3.composables/6.useSanctumConfig.md ================================================ --- title: useSanctumConfig description: How to use useSanctumConfig to retrieve module configuration navigation: icon: i-lucide-parentheses --- ## Usage This composable provides quick access to the module configuration instead of using `useRuntimeConfig` and several keys like `public.sanctum`. The composable is **context-aware** - it automatically returns the appropriate configuration based on where it's executed: - **Server-side (SSR)**: Returns `runtimeConfig.sanctum` configuration - **Client-side (CSR)**: Returns `runtimeConfig.public.sanctum` configuration This allows you to have different settings for server and client contexts (e.g., different `baseUrl` for Docker internal network vs public TLD). ```typescript const config = useSanctumConfig(); // On server: runtimeConfig.sanctum.baseUrl // On client: runtimeConfig.public.sanctum.baseUrl console.log(config.baseUrl); ``` More details about the configuration structure can be found [here](/usage/configuration). ================================================ FILE: docs/content/3.composables/7.useSanctumAppConfig.md ================================================ --- title: useSanctumAppConfig description: How to use useSanctumAppConfig to retrieve module application configuration navigation: icon: i-lucide-parentheses --- ## Usage This composable provides quick access to the module configuration instead of using `useAppConfig().sanctum`. Take a look at the following example ```typescript const config = useSanctumAppConfig(); console.log(config.interceptors.onRequest); // appConfig.sanctum.interceptors.onRequest ``` More details about the configuration structure can be found [here](/usage/configuration). ================================================ FILE: docs/content/4.middleware/1.sanctum-auth.md ================================================ --- title: sanctum:auth description: How to protect pages from unauthenticated users navigation: icon: i-lucide-user-lock --- ## Usage This middleware checks if the user is authenticated. If not, it will redirect a user to the page specified in the `redirect.onAuthOnly` option (default is `/login`). Also, you might want to remember what page the **user was trying to access** and redirect him back to that page after successful authentication. To do that, just enable the `redirect.keepRequestedRoute` option and it will be automatically stored in the URL for later redirect. If there is no redirect rule the middleware will throw `403` error. ## Example This is an example of middleware usage ```vue [app/pages/dashboard.vue] ``` ================================================ FILE: docs/content/4.middleware/2.sanctum-guest.md ================================================ --- title: sanctum:guest description: How to protect pages from authenticated users navigation: icon: i-lucide-lock-open --- ## Usage This middleware checks if the user is not authenticated. If not, it will redirect a user to the page specified in the `redirect.onGuestOnly` option (default is `/`). If there is no redirect rule the middleware will throw `403` error. ## Example This is an example of middleware usage ```vue [app/pages/login.vue] ``` ================================================ FILE: docs/content/4.middleware/3.global.md ================================================ --- title: Global middleware description: How to configure global middleware for the whole Nuxt application navigation: icon: i-lucide-shield-check --- ## Usage Instead of usage `sanctum:auth` and `sanctum:guest` on each page, you can enable global middleware that checks every route and restricts unauthenticated access. The behavior of this middleware is the same as [global middleware](https://nuxt.com/docs/guide/directory-structure/middleware) in Nuxt applications. ::warning Once global middleware is enabled, you can no longer use `sanctum:auth` and `sanctum:guest` on your pages. :: ## Configuration To enable middleware, use the following configuration in your `nuxt.config.ts` ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: [ 'nuxt-auth-sanctum' ], sanctum: { baseUrl: 'http://localhost:80', redirect: { onAuthOnly: '/login', onGuestOnly: '/profile', }, globalMiddleware: { enabled: true, }, } }) ``` Keep in mind, you must define `onAuthOnly` and `onGuestOnly` routes to help the plugin understand which page should be excluded from the middleware. - `onAuthOnly` - this route is used to redirect unauthenticated users to let them log in, similar to `sanctum:auth` middleware - `onGuestOnly` - this route is used to redirect already authenticated users, similar to `sanctum:guest` middleware ::tip You can also set `globalMiddleware.prepend` to true to load it before any other middleware. :: ## Exceptions If you want to exclude an additional page besides `onAuthOnly` route, then you can define page metadata like in the example below: ```typescript definePageMeta({ sanctum: { excluded: true, } }) ``` This page will not be checked by global middleware regardless of user authentication status. ## Guest mode Sometimes, you may have more than one page which are available only for unauthenticated users, for instance: - "Sign up" page - "Forgot my password" page In these situations, you can use `sanctum.guestOnly` property of the page meta: ```typescript definePageMeta({ sanctum: { guestOnly: true, } }) ``` ::warning Keep in mind, that those pages still will be handled by global middleware to check the user authentication state, so for public pages, it is still better to use `sanctum.excluded` to speed up the loading process. :: ## Non-existing routes By default, when a user requests a non-existing route an error page will be thrown with 404 status, but you can also enable redirect to `onAuthOnly` instead by setting `allow404WithoutAuth` to `false`. ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: [ 'nuxt-auth-sanctum', ], sanctum: { baseUrl: 'http://localhost:80', redirect: { onAuthOnly: '/login', onGuestOnly: '/profile', }, globalMiddleware: { enabled: true, allow404WithoutAuth: false, }, } }) ``` ================================================ FILE: docs/content/5.hooks/1.sanctum-request.md ================================================ --- title: sanctum:request description: Subscribe to request hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage When your Nuxt application sends any request against the Laravel API, you can subscribe to the `sanctum:request` hook the same way as the ofetch interceptor `onRequest`. ::tip More details about interceptors can be found here - [interceptors](/advanced/interceptors). :: ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:request', (nuxtApp, context, logger) => { logger.info('Sanctum request hook triggered', context.request) }) }) ``` Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers on every client request. */ 'sanctum:request': (app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance) => HookResult } ``` ================================================ FILE: docs/content/5.hooks/10.sanctum-proxy-request.md ================================================ --- title: sanctum:proxy:request description: Subscribe to request hook to inject custom event handling for proxy endpoint navigation: icon: i-lucide-webhook --- ## Usage ::warning This hook works only when you use [Server Proxy](/usage/proxy) endpoint. :: When your Nuxt application sends any request against the Laravel API via proxy endpoint, you can subscribe to the `sanctum:proxy:request` hook the same way as the ofetch interceptor `onRequest`. ::tip More details about interceptors can be found here - [interceptors](/advanced/interceptors). :: ```typescript [server/plugins/sanctum-listener.ts] export default defineNitroPlugin((nuxtApp) => { nitroApp.hooks.hook("sanctum:proxy:request", (context, logger) => { logger.info("Sanctum proxy request hook triggered", context.request); }); }); ``` Here is what the hook looks like ```typescript interface NitroRuntimeHooks { /** * Triggers on every client proxy request. */ "sanctum:proxy:request": (ctx: FetchContext, logger: ConsolaInstance) => void; } ``` ================================================ FILE: docs/content/5.hooks/2.sanctum-response.md ================================================ --- title: sanctum:response description: Subscribe to response hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage When your Laravel API returns any response, you can subscribe to the `sanctum:response` hook the same way as the ofetch interceptor `onResponse`. ::tip More details about interceptors can be found here - [interceptors](/advanced/interceptors). :: ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:response', (nuxtApp, context, logger) => { logger.info('Sanctum response hook triggered', context.request) }) }) ``` Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers on every server response. */ 'sanctum:response': (app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance) => HookResult } ``` ================================================ FILE: docs/content/5.hooks/3.sanctum-error-request.md ================================================ --- title: sanctum:error:request description: Subscribe to request error hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage When you send a request to the API, it could raise an exception even before reaching the remote server. For these cases, `ofetch` uses `onRequestError`. All these errors are available via `sanctum:error:request` hook. ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:error:request', (context) => { console.log('Sanctum request error hook triggered', context) }) }) ``` Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers when receiving an error on fetch request. */ 'sanctum:error:request': (context: FetchContext) => HookResult } ``` ================================================ FILE: docs/content/5.hooks/4.sanctum-error-response.md ================================================ --- title: sanctum:error:response description: Subscribe to response error hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage When you send a request to Laravel API using any available module's composable, it may return an error, such as 401, 419, 403, 404, etc. ::tip By default, `nuxt-auth-sanctum` will try to redirect a user if 401 is returned. Unless you disable this by setting `sanctum.redirectIfUnauthenticated` to `false` in your `nuxt.config.ts` file. :: However, if you need more granular control over API errors, you can subscribe to the `sanctum:error` hook and process the HTTP response according to your requirements. ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:error:response', (response) => { console.log('Sanctum error hook triggered', response) }) }) ``` Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers when receiving an error response. */ 'sanctum:error:response': (response: FetchResponse) => HookResult } ``` ================================================ FILE: docs/content/5.hooks/5.sanctum-redirect.md ================================================ --- title: sanctum:redirect description: Subscribe to redirect hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage The module can apply redirects in different situations, like `onLogin` or `onLogout` and you can subscribe to this event to keep track of any redirect happening before it is done. Subscribe to the `sanctum:redirect` hook which receives the URL of the target path of a redirect. ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:redirect', (url) => { console.log('Sanctum redirect hook triggered', url) }) }) ``` Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers when user has been redirected. */ 'sanctum:redirect': (response: FetchResponse) => HookResult } ``` ================================================ FILE: docs/content/5.hooks/6.sanctum-init.md ================================================ --- title: sanctum:init description: Subscribe to init hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage Our module registers a plugin which requests a user identity once an application is started. This is needed for middleware and redirects to properly function. Subscribe to the `sanctum:init` hook which triggers once the identity request is completed. ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:init', () => { console.log('Sanctum init hook triggered') }) }) ``` ::warning Keep in mind, since `nuxt-auth-sanctum` is loaded before any other module/plugin, you might need to configure your own plugin and set dependencies as described in [Plugin dependencies](/advanced/dependencies). :: Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers when an initial user identity request has been made. */ 'sanctum:init': () => HookResult } ``` ================================================ FILE: docs/content/5.hooks/7.sanctum-refresh.md ================================================ --- title: sanctum:refresh description: Subscribe to refresh hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage When the authentication state changes (e.g. `onLogin`, `onLogout`), the module has to refresh the user identity. Subscribe to the `sanctum:refresh` hook which triggers once the identity refresh request is completed. ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:refresh', () => { console.log('Sanctum refresh hook triggered') }) }) ``` Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers when user identity has been refreshed. */ 'sanctum:refresh': () => HookResult } ``` ================================================ FILE: docs/content/5.hooks/8.sanctum-login.md ================================================ --- title: sanctum:login description: Subscribe to login hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage Subscribe to the `sanctum:login` hook which triggers once the user is logged in and the identity refresh request is completed. ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:login', () => { console.log('Sanctum login hook triggered') }) }) ``` Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers when user successfully logs in. */ 'sanctum:login': () => HookResult } ``` ================================================ FILE: docs/content/5.hooks/9.sanctum-logout.md ================================================ --- title: sanctum:logout description: Subscribe to logout hook to inject custom event handling navigation: icon: i-lucide-webhook --- ## Usage Subscribe to the `sanctum:logout` hook which triggers once the user is logged out and the identity reset is done. ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:logout', () => { console.log('Sanctum logout hook triggered') }) }) ``` Here is what the hook looks like ```typescript interface RuntimeNuxtHooks { /** * Triggers when user successfully logs out. */ 'sanctum:logout': () => HookResult } ``` ================================================ FILE: docs/content/6.advanced/1.interceptors.md ================================================ --- title: Interceptors description: Use custom interceptors for the fetch client used for API calls navigation: icon: i-lucide-split --- ## Usage Interceptors allow you to define custom functions that will be used by [sanctumClient](/composables/usesanctumclient) during API calls. Here are some examples of what you can do with it: - Add custom headers to all requests (e.g. `X-Localization`, `Accept-Language`, etc) - Use telemetry or logging for requests/responses - Modify the request payload before sending ::warning If you are not familiar with [ofetch](https://github.com/unjs/ofetch) interceptors, check this [documentation](https://github.com/unjs/ofetch?tab=readme-ov-file#%EF%B8%8F-interceptors) first. :: ## Configuration This module provides special hooks to define your interceptors: - `sanctum:request` - `sanctum:response` You can set up a new plugin and describe the behaviour of handling each outgoing request and incoming response. Here is an example of the plugin that writes a log entry for each request and response: ```typescript [app/plugins/sanctum-listener.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:request', (app, ctx, logger) => { logger.info('Sanctum request hook triggered', ctx.request) }) nuxtApp.hook('sanctum:response', (app, ctx, logger) => { logger.info('Sanctum response hook triggered', ctx.request) }) }) ``` Each interceptor receives 3 arguments: 1. `app` - an instance of the current `NuxtApp` 2. `ctx` - `FetchContext` instance for the current operation with access to request, response, and options (*query, headers, etc*) 3. `logger` - an instance of a Consola logger used by the module (will be prefixed with `nuxt-auth-sanctum`) ================================================ FILE: docs/content/6.advanced/2.error-handling.md ================================================ --- title: Error handling description: How to catch Laravel API errors using this module navigation: icon: i-lucide-bug --- ## Usage Error handling of API responses is not a part of this module since the main goal is to provide an authentication layer and configured API client, but on this page, you can find useful hints. When Laravel returns an error of any kind (*403, 404, 500, etc*), the module will throw this as an exception that has a generic `Error` type. ::tip By default, when Laravel API returns a `401` status code, **the module will reset the user identity** and redirect to `sanctum.redirect.onAuthOnly` route (if `sanctum.redirectIfUnauthenticated` is enabled). :: ## Error type check This is how you can check what type of error you received ```typescript import { FetchError } from 'ofetch' const { login } = useSanctumAuth() const userCredentials = { email: 'user@mail.com', password: '123123', } async function onCredentialsFormSubmit() { try { await login(userCredentials) } catch (e) { if (error instanceof FetchError && error.response?.status === 422) { // here you can extract errors from a response // and put it in your form for example console.log(e.response?._data.errors) } } } ``` Sometimes, it is not convenient, especially when it comes to validation errors in plenty of forms and components. ## Error helper Here you can get inspiration from error handling specifically for this case and implement it your way. Create a new composable `useApiError` with the following content: ```typescript [app/composables/useApiError.ts] import { FetchError } from 'ofetch' const VALIDATION_ERROR_CODE = 422 const SERVER_ERROR_CODE = 500 export const useApiError = (error: any) => { const isFetchError = error instanceof FetchError const isValidationError = isFetchError && error.response?.status === VALIDATION_ERROR_CODE const code = isFetchError ? error.response?.status : SERVER_ERROR_CODE const bag: Record = isValidationError ? error.response?._data.errors : {} return { isValidationError, code, bag, } } ``` Use it as in the next example to extract all the errors from the response and handle it according to your logic: ```typescript try { await login(credentials) } catch (e) { const error = useApiError(e) if (error.isValidationError) { form.setErrors(error.bag) return } console.error('Request failed not because of a validation', error.code) } ``` Have a good debugging! 😎 ================================================ FILE: docs/content/6.advanced/3.logging.md ================================================ --- title: Logging description: How to extract useful debug information from the plugin navigation: icon: i-lucide-logs --- ## Usage Sometimes it might be useful to check the system logs and messages from the plugin, especially if you want to check what headers and cookies are being sent. All messages will be written in the same manner as regular `console.log` messages and can be checked in the browser (for CSR) or in the Node console (for SSR). By default, the plugin uses `3` as logging level and shows error and informational logs without debugging details. You can override `sanctum.logLevel` parameter in the `nuxt.config.ts` and set one of these levels: - 0 - Fatal and Error - 1 - Warnings - 2 - Normal logs - 3 - Informational logs - 4 - Debug logs - 5 - Trace logs It follows the convention of the Consola project, more details can be found here - [Log Level](https://github.com/unjs/consola?tab=readme-ov-file#log-level). ================================================ FILE: docs/content/6.advanced/4.token-storage.md ================================================ --- title: Token Storage description: How to work with storage with enabled token-based authentication navigation: icon: i-lucide-vault --- ## Usage Token storage is used for keeping authentication token value from the Laravel API available for module consumption during requests assembling. Storage is used only when `sanctum.mode` equals to `token` in your nuxt configuration: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ modules: ["nuxt-auth-sanctum"], sanctum: { // ... mode: "token", // ... }, }); ``` ::warning By default, if there is no custom token storage defined, cookies will be used. :: ## How it works Each token storage implements the following interface: ```typescript /** * Handlers to work with authentication token. */ export interface TokenStorage { /** * Function to load a token from the storage. */ get: (app: NuxtApp) => Promise; /** * Function to save a token to the storage. */ set: (app: NuxtApp, token?: string) => Promise; } ``` After the user sends credentials to the API module passes a token from the response to `set` method as well as the current Nuxt application instance to allow calls like `app.runWithConext()`. Once the user logs out, the module sends `undefined` as a token value to reset the stored value. Before each request against the API, the module loads the token by calling get method with Nuxt instance passed. ## Define token storage There are two approaches to define custom token storage: ### Using the `sanctum:storage:token` Hook (Recommended) The recommended way to define custom token storage is using the `sanctum:storage:token` hook. This approach works with all builds including `nuxt generate` (e.g. static builds for Capacitor/Ionic apps). ```typescript [plugins/sanctum-storage.client.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook("sanctum:storage:token", () => { const storage = { async get(app: NuxtApp) { const { Preferences } = await import("@capacitor/preferences"); const result = await Preferences.get({ key: "sanctum.token" }); return result.value ?? undefined; }, async set(app: NuxtApp, token?: string) { const { Preferences } = await import("@capacitor/preferences"); if (token) { await Preferences.set({ key: "sanctum.token", value: token }); } else { await Preferences.remove({ key: "sanctum.token" }); } }, }; useSanctumTokenStorage(storage); }); }); ``` ::tip For Capacitor/Ionic apps, this is the **only** approach that works with `nuxt generate` because plugins are compiled by Vite into the JavaScript bundle, preserving the functions. :: ### Using app.config.ts You can define your own handler in the `app.config.ts` configuration file: ::warning The `app.config.ts` approach only works in **dev mode** and **Node.js production builds**. When using `nuxt generate` (static builds for platforms like Capacitor/Ionic), functions are stripped during JSON serialization and the custom tokenStorage won't work. :: ```typescript [app/app.config.ts] // LocalStorage example for Laravel Authentication token const tokenStorageKey = "sanctum.storage.token"; const localTokenStorage: TokenStorage = { get: async () => { if (import.meta.server) { return undefined; } return window.localStorage.getItem(tokenStorageKey) ?? undefined; }, set: async (app: NuxtApp, token?: string) => { if (import.meta.server) { return; } if (!token) { window.localStorage.removeItem(tokenStorageKey); return; } window.localStorage.setItem(tokenStorageKey, token); }, }; export default defineAppConfig({ sanctum: { tokenStorage: localTokenStorage, }, }); ``` Now your application will store tokens in a local storage of your browser. ::warning Keep in mind, `localStorage` is not available for SSR mode, so you should turn it off in your `nuxt.config.ts`. :: ## When to use which approach? | Approach | Works with `nuxt dev` | Works with SSR | Works with `nuxt generate` | | --------------------------------- | --------------------- | -------------- | -------------------------- | | Hook + `useSanctumTokenStorage()` | Yes | Yes | Yes | | `app.config.ts` | Yes | Yes | No | ================================================ FILE: docs/content/6.advanced/5.dependencies.md ================================================ --- title: Plugin dependencies description: Configuration of plugin dependencies to control load order navigation: icon: i-lucide-link --- ## Usage Sometimes you might need to use other plugins while making requests against your Laravel API, for instance - `i18n` headers enrichment on each sanctum fetch request like this: ```typescript [app/plugins/sanctum-plugin.ts] export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:request', (app, ctx, logger) => { ctx .options .headers .set("X-Language", app.$i18n.localeProperties.value.code) }) }) ``` Since this module cannot know about its dependencies in your application, you should use one of the following approaches to configure this behaviour: - use `sanctum.appendPlugin` to register the Sanctum client plugin only after the previous modules are registered already - disable an automatic initial user request and call it from your custom plugin with a properly set list of dependencies ## Append plugin By default, all Nuxt plugins registered by the module use `prepend` operation on a list of plugins, which makes it load before other plugins. To change this behaviour, you can set the `sanctum.appendPlugin` config key to `true` and see that the sanctum plugin will be registered after most of the plugins from other modules. This is done by using `append` operation instead of `prepend` on the list of plugins. For more details, please check the Nuxt documentation [here](https://nuxt.com/docs/api/kit/plugins#options). ## Manual initial identity request Even after changing the loading order of the plugin, there might be some cases when you need more granular control of the execution flow of the initial identity requests. For these kinds of situations, you should disable a plugin initialization request by setting `sanctum.client.initialRequest` to `false` and use it in your custom plugin like this: ```typescript [app/plugins/custom-auth.ts] export default defineNuxtPlugin({ name: 'custom-auth', dependsOn: ['@nuxtjs/i18n', 'nuxt-auth-sanctum'], async setup() { nuxtApp.hook('sanctum:request', (app, ctx, logger) => { ctx .options .headers .set("X-Language", app.$i18n.localeProperties.value.code) }) const { init } = useSanctumAuth() await init() } }) ``` This approach can guarantee that the user's identity will be requested with all dependent plugins loaded properly. ::warning Beware, in the case of using a custom plugin for identity initial requests, you might need to handle API errors on your own (e.g. 401, 419) due to missing CSRF cookie values. You can check the default implementation for reference - [identity request error handling](https://github.com/manchenkoff/nuxt-auth-sanctum/blob/main/src/runtime/plugin.ts#L62). :: ================================================ FILE: docs/content/6.advanced/6.breeze-nuxt-template.md ================================================ --- title: Breeze Nuxt Template description: A quick introduction to the application template based on Nuxt for the Laravel Sanctum API backend. navigation: icon: i-simple-icons-nuxt --- ## Application template Suppose you want to start a fresh project based on Nuxt and Laravel Sanctum with Laravel Echo integration. In that case, you may consider trying out the template repository that has implemented Echo integration and all authentication logic and also contains several pages such as: - Landing - Login - Sign up - Password reset - Dashboard The repository is available here - [breeze-nuxt](https://github.com/manchenkoff/breeze-nuxt), follow the guide in the `readme.md` file to set up Laravel API and connect it to the front-end application. Also, it uses the Nuxt UI module that allows you to start building complex interfaces with ease thanks to predefined components and Tailwind CSS. For more details, check the repository. As for the backend API part, we also have you covered. Check out our [breeze-api](https://github.com/manchenkoff/breeze-api) template. ================================================ FILE: docs/content/6.advanced/7.troubleshooting.md ================================================ --- title: Troubleshooting description: Follow this guide in case you are experiencing unexpected behaviour or facing some errors in the module work. navigation: icon: i-lucide-microscope --- ## Usage Since Laravel Sanctum requires a specific configuration for your application, your production might work differently in comparison to a local development environment. On this page, you can find a description of the most common issues raised on GitHub and how to solve them by adjusting either your Laravel or Nuxt application configurations. ## Common problems First of all, if you experience any unexpected behaviour, we recommend enabling `logLevel: 5` in your `nuxt.config.ts` to get more details in SSR (server console) or CSR (browser console) output. For more details about logging, please refer to this page - [Logging](/advanced/logging). In case of misconfiguration on either Nuxt or Laravel side, you may experience: - Authentication state reset on page reload - Difference between CSR and SSR state - Errors while trying to log in - Failing subsequent requests after successfully logging in ::tip Before searching for a solution to your problem, we highly recommend double-checking your configuration and ensuring that the cache is cleared and your runtime reflects the latest changes. :: ## Known problems Below you can find the description of the most popular issues, which were already resolved. ### Works on client-side (CSR), but not on server-side (SSR) If you have SSR enabled, our module sends some of the requests on plugin initialisation before returning content to the client, which means we have to proxy some initial request data which might work differently on the server side. For example, to fetch user identity we are passing cookies from the initial client request. ::warning Since CSR and SSR requests are sent from different environments, you have to ensure that your API is accessible from both of them and Laravel allow accepting requests from CSR/SSR hosts. :: If you use a Docker container, make sure that the `sanctum.baseUrl` in your `nuxt.config.ts` is accessible from both your web browser and the Nuxt container. We recommend using a domain name as the name of the container in the virtual network to avoid side effects. ::tip You can check the example here - [breeze-api](https://github.com/manchenkoff/breeze-api/blob/main/docker-compose.yml#L2). :: In case you use additional software to set up virtual domains for development purposes (e.g. Laravel Valet, Homestead, dnsmasq, etc), you may end up with incorrect DNS resolving by Node. We recommend to use `localhost` domain with different ports instead. ### Request blocked by CORS policy Incorrect CORS configuration on the Laravel side can cause the following problem ::caution Access to fetch at 'X' from origin 'Y' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'Z' that is not equal to the supplied origin. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. :: In this case, your Nuxt application is calling API endpoint **X** from host **Y**, which is not the same as **Z** configured as `allowed_origins` in Laravel's `config/cors.php`. If you are using Laravel Breeze, then adjusting `FRONTEND_URL` environment variable would be enough. ### Unable to load user identity from API (Code 500 / 403) If Nuxt cannot retrieve user identity on plugin initialization, that means that either your API is not reachable or there is an endpoint misconfiguration. For example, the following error means that `fetch` could not find a host with `laravel.test` URL due to network problems. ``` Unable to load user identity from API [GET] "https://laravel.test/api/user": fetch failed ``` You should double-check: - URL exists and is reachable, - schema is chosen correctly (*http/https*), - API port is set correctly (e.g. *80, 8080, 8000, 3000*) - a Docker container is up and running (if applicable), - `artisan serve` is using `localhost` instead of `127.0.0.1` (if applicable) Also, while working locally with enabled SSL, you may face the following error: ``` [nuxt-auth-sanctum:ssr] ERROR Unable to load user identity from API [GET] "https://laravel.test/api/user": fetch failed [cause]: fetch failed [cause]: unable to verify the first certificate ``` To enable HTTPS protocol, you might need to set an environment variable `NODE_TLS_REJECT_UNAUTHORIZED=0`. ### Page hangs on load with infinite SSR requests when using only NUXT_PUBLIC_SANCTUM_BASE_URL If your app never finishes loading and your terminal shows an infinite loop of SSR plugin setup and requests to your base URL with no response, this is likely the cause. This happens because you defined your API URL in your `.env` file using **only** `NUXT_PUBLIC_SANCTUM_BASE_URL`. Nuxt strictly isolates public and private runtime configurations. When the server attempts to fetch your user identity during SSR, it cannot see the public variable and the request never resolves. **Fix:** Add `NUXT_SANCTUM_BASE_URL=http://your-api-url` to your `.env` file alongside the public one to ensure the server context uses the correct URL. ### User is not authenticated on plugin initialization (Code 401) With enabled logging, you can check your Nuxt logs to find errors and warnings about the reason for the 401 response. For example, if you see the following message there: ``` [nuxt-auth-sanctum:ssr] WARN [response] set-cookie header is missing [nuxt-auth-sanctum:ssr] ⚙ User is not authenticated on plugin initialization, status: 401 ``` then you should check your SANCTUM_STATEFUL_DOMAINS environment variable on the Laravel side. If you have a domain different than your Nuxt application is hosted on, it can cause an issue. ### CSRF mismatch (Code 419) In the logs you can see this entry - **`CSRF token mismatch, check your API configuration`**. This error usually occurs if your API returns a 419 status code, meaning Laravel expects a different cookie value which in most cases can be solved by adjusting the `SANCTUM_STATEFUL_DOMAINS` or `SESSION_DOMAIN` environment variables in your Laravel application. Keep in mind, that Laravel supports cookies only from the same TLD, meaning you cannot call your API from a different domain. For instance: - frontend app - `https://myapp.com` - backoffice app - `https://admin.myapp.com` - Laravel API - `https://api.myapp.com` In this setup, `SESSION_DOMAIN` should be `.myapp.com` and `SANCTUM_STATEFUL_DOMAINS` should be `myapp.com,admin.myapp.com`. ::warning If you want to use token authentication, make sure to remove your frontend application from stateful domains to avoid CSRF-check middleware. :: ### Missing headers in the API request If you use `routeRules` and do not see Nuxt passing some of the expected headers to your Laravel API, it might be because of proxying behaviour, which is a bit different from the direct fetch request. Make sure that you also define supported headers in your `nuxt.config.ts` like this: ```typescript [nuxt.config.ts] export default defineNuxtConfig({ // ... other config routeRules: { '/backend/api/**': { proxy: { to: `http://laravel.test/api/**`, headers: { YOUR_HEADER: 'header_value' }, } } } }) ``` If you could not find anything useful, please check the [Issues](https://github.com/manchenkoff/nuxt-auth-sanctum/issues?q=is%3Aissue%20state%3Aclosed) section on GitHub or feel free to [create a new one](https://github.com/manchenkoff/nuxt-auth-sanctum/issues/new?template=bug_report.md)! ================================================ FILE: docs/content/index.md ================================================ --- seo: title: Nuxt - Laravel Sanctum description: This module provides a simple way to use Laravel Sanctum with Nuxt. SSR-ready! --- ::u-page-hero{class="dark:bg-gradient-to-b from-neutral-900 to-neutral-950"} --- orientation: horizontal --- #top :hero-background #title Authenticate users [easily]{.text-primary}. #description The only module you need to set up Laravel Sanctum authentication for Nuxt application! #links :::u-button --- to: /getting-started size: xl trailing-icon: i-lucide-arrow-right --- Get started ::: :::u-button --- icon: i-simple-icons-github color: neutral variant: outline size: xl to: https://github.com/sponsors/manchenkoff?o=esb target: _blank --- Support project ::: :::u-button --- icon: i-simple-icons-buymeacoffee color: neutral variant: outline size: xl to: https://buymeacoffee.com/manchenkoff target: _blank --- Buy me a coffee ::: #default :::prose-pre --- code: npx nuxi@latest module add nuxt-auth-sanctum filename: Install module --- ```bash npx nuxi@latest module add nuxt-auth-sanctum ``` ::: :: ::u-page-section{class="dark:bg-neutral-950"} #title Features #links :::u-button --- color: neutral size: lg to: /getting-started trailingIcon: i-lucide-arrow-right variant: subtle --- Explore Nuxt Laravel Sanctum ::: #features :::u-page-feature --- icon: i-lucide-cookie --- #title Cookie or Token #description Automated handling `CSRF` cookies and `Bearer` tokens for authentication ::: :::u-page-feature --- icon: i-lucide-user-lock --- #title Focus on logic, authentication is on us #description We provide a lot of useful composables, middleware, fetch utilities, hooks and more... ::: :::u-page-feature --- icon: i-lucide-layers --- #title CSR + SSR #description Module supports both client and server rendering! ::: :::u-page-feature --- icon: i-simple-icons-typescript --- #title TypeScript #description Code of this module is written entirely in TypeScript and supports autocompletion ::: :::u-page-feature --- icon: i-lucide-cog --- #title No complex configuration #description You literally can start using this module by just providing Laravel API URL ::: :::u-page-feature --- icon: i-lucide-git-pull-request --- #title Open-source #description Source code is forever-free and open for contributions! ::: :: ::u-page-section{class="dark:bg-gradient-to-b from-neutral-950 to-neutral-900"} :::u-page-c-t-a --- links: - label: Start authenticating to: '/getting-started' trailingIcon: i-lucide-arrow-right - label: View on GitHub to: 'https://github.com/manchenkoff/nuxt-auth-sanctum' target: _blank variant: subtle icon: i-simple-icons-github title: Ready to build? description: Authenticate users with Laravel, Sanctum and Nuxt today! class: dark:bg-neutral-950 --- :stars-background ::: :: ================================================ FILE: docs/content.config.ts ================================================ import { defineContentConfig, defineCollection, z } from '@nuxt/content' export default defineContentConfig({ collections: { landing: defineCollection({ type: 'page', source: 'index.md' }), docs: defineCollection({ type: 'page', source: { include: '**', exclude: ['index.md'] }, schema: z.object({ links: z.array(z.object({ label: z.string(), icon: z.string(), to: z.string(), target: z.string().optional() })).optional() }) }) } }) ================================================ FILE: docs/eslint.config.mjs ================================================ // @ts-check import withNuxt from './.nuxt/eslint.config.mjs' export default withNuxt( // Your custom configs here ) ================================================ FILE: docs/nuxt.config.ts ================================================ export default defineNuxtConfig({ modules: [ '@nuxt/eslint', '@nuxt/image', '@nuxt/ui', '@nuxt/content', 'nuxt-og-image', 'nuxt-llms' ], devtools: { enabled: true }, app: { head: { script: [ { src: 'https://media.bitterbrains.com/main.js?from=ARTEM&type=top', defer: true, async: true } ] } }, css: ['~/assets/css/main.css'], content: { build: { markdown: { toc: { searchDepth: 1 } } } }, nitro: { prerender: { routes: [ '/' ], crawlLinks: true, autoSubfolderIndex: false } }, eslint: { config: { stylistic: { commaDangle: 'never', braceStyle: '1tbs' } } }, icon: { provider: 'iconify' }, llms: { domain: 'https://sanctum.manchenkoff.me', title: 'Nuxt - Laravel Sanctum', description: 'The only module you need to set up Laravel Sanctum authentication for Nuxt application!', full: { title: 'Nuxt - Laravel Sanctum Module Documentation', description: 'This is the full documentation for Nuxt Laravel Sanctum module.' }, sections: [ { title: 'Getting Started', contentCollection: 'docs', contentFilters: [ { field: 'path', operator: 'LIKE', value: '/getting-started%' } ] }, { title: 'Usage', contentCollection: 'docs', contentFilters: [ { field: 'path', operator: 'LIKE', value: '/usage%' } ] }, { title: 'Composables', contentCollection: 'docs', contentFilters: [ { field: 'path', operator: 'LIKE', value: '/composables%' } ] }, { title: 'Middleware', contentCollection: 'docs', contentFilters: [ { field: 'path', operator: 'LIKE', value: '/middleware%' } ] }, { title: 'Hooks', contentCollection: 'docs', contentFilters: [ { field: 'path', operator: 'LIKE', value: '/hooks%' } ] }, { title: 'Advanced', contentCollection: 'docs', contentFilters: [ { field: 'path', operator: 'LIKE', value: '/advanced%' } ] } ] } }) ================================================ FILE: docs/package.json ================================================ { "name": "nuxt-auth-sanctum-docs", "private": true, "type": "module", "scripts": { "build": "nuxt build", "generate": "nuxt generate", "dev": "nuxt dev", "preview": "nuxt preview", "postinstall": "nuxt prepare", "lint": "eslint .", "lint:fix": "eslint . --fix", "upgrade": "pnpm self-update && npx nuxt upgrade --dedupe && pnpm up && pnpm lint" }, "dependencies": { "@iconify-json/lucide": "^1.2.103", "@iconify-json/simple-icons": "^1.2.79", "@iconify-json/vscode-icons": "^1.2.46", "@nuxt/content": "^3.13.0", "@nuxt/image": "^1.11.0", "@nuxt/ui": "^4.7.0", "better-sqlite3": "^12.9.0", "nuxt": "^4.4.2", "nuxt-llms": "0.1.3", "nuxt-og-image": "^6.4.8" }, "devDependencies": { "@nuxt/eslint": "^1.15.2", "@takumi-rs/core": "^0.73.1", "eslint": "^10.2.1", "typescript": "^5.9.3", "vue-tsc": "^3.2.7" }, "resolutions": { "unimport": "4.1.1" }, "packageManager": "pnpm@10.33.0" } ================================================ FILE: docs/pnpm-workspace.yaml ================================================ ignoredBuiltDependencies: - "@parcel/watcher" - "@tailwindcss/oxide" - esbuild - unrs-resolver - vue-demi onlyBuiltDependencies: - better-sqlite3 - sharp ================================================ FILE: docs/renovate.json ================================================ { "extends": ["github>nuxt/renovate-config-nuxt"], "lockFileMaintenance": { "enabled": true }, "packageRules": [ { "matchDepTypes": ["resolutions"], "enabled": false } ], "postUpdateOptions": ["pnpmDedupe"] } ================================================ FILE: docs/tsconfig.json ================================================ { "files": [], "references": [ { "path": "./.nuxt/tsconfig.app.json" }, { "path": "./.nuxt/tsconfig.server.json" }, { "path": "./.nuxt/tsconfig.shared.json" }, { "path": "./.nuxt/tsconfig.node.json" } ] } ================================================ FILE: eslint.config.mjs ================================================ // @ts-check import { createConfigForNuxt } from '@nuxt/eslint-config/flat' export default createConfigForNuxt( { features: { tooling: true, stylistic: true, }, dirs: { src: ['./playground'], }, }, // { rules: { 'vue/no-multiple-template-root': 'off', 'vue/multi-word-component-names': 'off', '@stylistic/eol-last': 'off', }, }, // { ignores: ['docs/'], }, ) ================================================ FILE: package.json ================================================ { "name": "nuxt-auth-sanctum", "version": "2.3.4", "author": { "name": "Artem Manchenkov", "email": "artem@manchenkoff.me", "url": "https://github.com/manchenkoff" }, "description": "Nuxt module for Laravel Sanctum authentication", "homepage": "https://sanctum.manchenkoff.me", "repository": { "type": "git", "url": "git+https://github.com/manchenkoff/nuxt-auth-sanctum.git" }, "license": "MIT", "type": "module", "exports": { ".": { "types": "./dist/types.d.mts", "import": "./dist/module.mjs" } }, "main": "./dist/module.mjs", "files": [ "dist" ], "scripts": { "prepack": "nuxt-module-build build", "rc": "pnpm pack --pack-destination dist", "dev": "nuxi dev playground", "dev:build": "nuxi build playground", "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "vitest run", "test:watch": "vitest watch", "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit", "validate": "npm run lint && npm run test:types && npm run test", "release": "npm run validate && npm run prepack && changelogen --release && npm publish && git push --follow-tags", "upgrade": "pnpm self-update && npx nuxt upgrade --dedupe && pnpm up && pnpm dev:prepare && pnpm validate" }, "dependencies": { "defu": "^6.1.7", "ofetch": "^1.5.1", "pathe": "^2.0.3" }, "devDependencies": { "@nuxt/devtools": "^3.2.4", "@nuxt/eslint-config": "^1.15.2", "@nuxt/kit": "^4.4.2", "@nuxt/module-builder": "^1.0.2", "@nuxt/schema": "^4.4.2", "@nuxt/test-utils": "^4.0.2", "@types/node": "^25.6.0", "changelogen": "^0.6.2", "consola": "^3.4.2", "eslint": "^10.2.1", "h3": "1.15.11", "nuxt": "^4.4.2", "typescript": "^5.9.3", "vitest": "^4.1.5", "vue": "^3.5.33", "vue-tsc": "^3.2.7" }, "packageManager": "pnpm@10.32.1", "pnpm": { "onlyBuiltDependencies": [ "@parcel/watcher", "esbuild", "unrs-resolver" ] } } ================================================ FILE: playground/app/app.vue ================================================ ================================================ FILE: playground/app/error.vue ================================================ ================================================ FILE: playground/app/layouts/default.vue ================================================ ================================================ FILE: playground/app/pages/index.vue ================================================ ================================================ FILE: playground/app/pages/login.vue ================================================ ================================================ FILE: playground/app/pages/logout.vue ================================================ ================================================ FILE: playground/app/pages/profile.vue ================================================ ================================================ FILE: playground/app/pages/welcome.vue ================================================ ================================================ FILE: playground/app/plugins/sanctum.hooks.ts ================================================ export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:error:response', (response) => { console.log('Sanctum error hook triggered', response) }) nuxtApp.hook('sanctum:error:request', (context) => { console.log('Sanctum request error hook triggered', context) }) nuxtApp.hook('sanctum:request', (_nuxtApp, context, logger) => { logger.info('Sanctum request hook triggered', context.request) }) nuxtApp.hook('sanctum:response', (_nuxtApp, context, logger) => { logger.info('Sanctum response hook triggered', context.request) }) nuxtApp.hook('sanctum:redirect', (url) => { console.log('Sanctum redirect hook triggered', url) }) nuxtApp.hook('sanctum:init', () => { console.log('Sanctum init hook triggered') }) nuxtApp.hook('sanctum:refresh', () => { console.log('Sanctum refresh hook triggered') }) nuxtApp.hook('sanctum:login', () => { console.log('Sanctum login hook triggered') }) nuxtApp.hook('sanctum:logout', () => { console.log('Sanctum logout hook triggered') }) }) ================================================ FILE: playground/app/plugins/sanctum.storage.client.ts ================================================ import type { NuxtApp } from '#app' import type { TokenStorage } from '../../../src/runtime/types/config' export default defineNuxtPlugin((nuxtApp) => { nuxtApp.hook('sanctum:storage:token', () => { const tokenStorageKey = 'sanctum.storage.token' const localTokenStorage: TokenStorage = { get: async () => { if (import.meta.server) { return undefined } return window.localStorage.getItem(tokenStorageKey) ?? undefined }, set: async (_app: NuxtApp, token?: string) => { if (import.meta.server) { return } if (!token) { window.localStorage.removeItem(tokenStorageKey) return } window.localStorage.setItem(tokenStorageKey, token) }, } useSanctumTokenStorage(localTokenStorage) }) }) ================================================ FILE: playground/nuxt.config.ts ================================================ export default defineNuxtConfig({ modules: ['../src/module'], ssr: true, devtools: { enabled: true }, future: { compatibilityVersion: 4, }, sanctum: { baseUrl: '/api/sanctum', mode: 'cookie', logLevel: 4, redirect: { keepRequestedRoute: true, onAuthOnly: '/login', onGuestOnly: '/profile', onLogin: '/welcome', onLogout: '/logout', }, endpoints: { csrf: '/sanctum/csrf-cookie', login: '/login', logout: '/logout', user: '/api/user', }, globalMiddleware: { allow404WithoutAuth: true, enabled: false, prepend: false, }, serverProxy: { enabled: true, route: '/api/sanctum', baseUrl: 'http://localhost:80', }, }, }) ================================================ FILE: playground/package.json ================================================ { "private": true, "name": "nuxt-auth-sanctum-playground", "type": "module", "scripts": { "dev": "nuxi dev", "build": "nuxi build", "generate": "nuxi generate", "upgrade": "pnpm self-update && npx nuxt upgrade --dedupe && pnpm up" }, "devDependencies": { "nuxt": "latest", "vue": "^3.5.33" } } ================================================ FILE: playground/pnpm-workspace.yaml ================================================ onlyBuiltDependencies: - esbuild ================================================ FILE: playground/server/plugins/sanctum.hooks.ts ================================================ import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import type { NitroApp } from 'nitropack' export default defineNitroPlugin((nitroApp: NitroApp): void => { nitroApp.hooks.hook('sanctum:proxy:request', (context: FetchContext, logger: ConsolaInstance) => { logger.info('Sanctum proxy request hook triggered', context.request) }) }) ================================================ FILE: playground/server/tsconfig.json ================================================ { "extends": "../.nuxt/tsconfig.server.json" } ================================================ FILE: playground/tsconfig.json ================================================ { "extends": "./.nuxt/tsconfig.json" } ================================================ FILE: src/config.ts ================================================ import type { ModuleOptions } from './runtime/types/options' export const defaultModuleOptions: ModuleOptions = { baseUrl: 'http://localhost:80', mode: 'cookie', userStateKey: 'sanctum.user.identity', redirectIfAuthenticated: false, redirectIfUnauthenticated: false, endpoints: { csrf: '/sanctum/csrf-cookie', login: '/login', logout: '/logout', user: '/api/user', }, csrf: { cookie: 'XSRF-TOKEN', header: 'X-XSRF-TOKEN', }, client: { retry: false, initialRequest: true, }, redirect: { keepRequestedRoute: false, onLogin: '/', onLogout: '/', onAuthOnly: '/login', onGuestOnly: '/', }, globalMiddleware: { enabled: false, prepend: false, allow404WithoutAuth: true, }, logLevel: 3, appendPlugin: false, serverProxy: { enabled: false, route: '/api/sanctum', baseUrl: 'http://localhost:80', }, } ================================================ FILE: src/module.ts ================================================ import { defineNuxtModule, addPlugin, createResolver, addImportsDir, addRouteMiddleware, useLogger, addServerHandler, } from '@nuxt/kit' import { defu } from 'defu' import { defaultModuleOptions } from './config' import type { PublicModuleOptions, ModuleOptions } from './runtime/types/options' import { registerTypeTemplates } from './templates' const MODULE_NAME = 'nuxt-auth-sanctum' export type ModuleRuntimeConfig = { sanctum: Partial } export type ModulePublicRuntimeConfig = { sanctum: Partial } export default defineNuxtModule({ meta: { name: MODULE_NAME, configKey: 'sanctum', }, defaults: defaultModuleOptions, setup(_options, _nuxt) { const resolver = createResolver(import.meta.url) const sanctumPublicConfig = defu( _nuxt.options.runtimeConfig.public.sanctum, _options, ) const { serverProxy: _, ...sanctumClientConfig } = sanctumPublicConfig const sanctumConfig = defu( _nuxt.options.runtimeConfig.sanctum, _nuxt.options.runtimeConfig.public.sanctum, _options, ) _nuxt.options.build.transpile.push(resolver.resolve('./runtime')) _nuxt.options.runtimeConfig.sanctum = sanctumConfig _nuxt.options.runtimeConfig.public.sanctum = sanctumClientConfig const logger = useLogger(MODULE_NAME, { level: sanctumConfig.logLevel }) addPlugin(resolver.resolve('./runtime/plugin'), { append: sanctumConfig.appendPlugin }) addImportsDir(resolver.resolve('./runtime/composables')) if (sanctumConfig.globalMiddleware.enabled) { addRouteMiddleware( { name: 'sanctum:auth:global', path: resolver.resolve('./runtime/middleware/sanctum.global'), global: true, }, { prepend: sanctumConfig.globalMiddleware.prepend, }, ) logger.info('Sanctum module initialized with global middleware') } else { addRouteMiddleware({ name: 'sanctum:auth', path: resolver.resolve('./runtime/middleware/sanctum.auth'), }) addRouteMiddleware({ name: 'sanctum:guest', path: resolver.resolve('./runtime/middleware/sanctum.guest'), }) logger.info('Sanctum module initialized w/o global middleware') } if (sanctumConfig.serverProxy.enabled) { addServerHandler({ route: `${sanctumConfig.serverProxy.route}/**`, handler: resolver.resolve('./runtime/server/api/proxy'), }) logger.info('Sanctum module initialized with server proxy') } registerTypeTemplates(resolver) }, }) ================================================ FILE: src/runtime/composables/useLazySanctumFetch.ts ================================================ import { type UseFetchOptions, useLazyFetch } from '#app' import { toRaw, toValue, type MaybeRefOrGetter } from 'vue' import { useSanctumClient } from '../composables/useSanctumClient' import type { SanctumFetchResponse } from '../types/fetch' export function useLazySanctumFetch( url: MaybeRefOrGetter, options?: Omit, 'lazy'>, ): SanctumFetchResponse { const client = useSanctumClient() as typeof $fetch const key = options?.key ?? JSON.stringify([toRaw(toValue(url)), toRaw(toValue(options))]) const params = { ...options, key, $fetch: client } as UseFetchOptions // @ts-expect-error unable to satisfy params return useLazyFetch(url, params) } ================================================ FILE: src/runtime/composables/useSanctumAppConfig.ts ================================================ import type { SanctumAppConfig } from '../types/config' import { useAppConfig } from '#imports' export const useSanctumAppConfig = (): SanctumAppConfig => { return (useAppConfig().sanctum ?? {}) as SanctumAppConfig } ================================================ FILE: src/runtime/composables/useSanctumAuth.ts ================================================ import { type ComputedRef, type Ref, computed } from 'vue' import { trimTrailingSlash } from '../utils/formatter' import { IDENTITY_LOADED_KEY } from '../utils/constants' import { useSanctumClient } from './useSanctumClient' import { useSanctumUser } from './useSanctumUser' import { useSanctumConfig } from './useSanctumConfig' import { useSanctumAppConfig } from './useSanctumAppConfig' import { navigateTo, useNuxtApp, useRoute, useState } from '#app' import type { SanctumFetchOptions } from '../types/fetch' export interface SanctumAuth { user: Ref isAuthenticated: ComputedRef init: () => Promise login: (credentials: Record, fetchIdentity?: boolean, options?: SanctumFetchOptions) => Promise logout: (options?: SanctumFetchOptions) => Promise refreshIdentity: () => Promise } export type TokenResponse = { token?: string } /** * Provides authentication methods for Laravel Sanctum * * @template T Type of the user object */ export const useSanctumAuth = (): SanctumAuth => { const nuxtApp = useNuxtApp() const user = useSanctumUser() const client = useSanctumClient() const config = useSanctumConfig() const appConfig = useSanctumAppConfig() const isAuthenticated = computed(() => { return user.value !== null }) const isIdentityLoaded = useState( IDENTITY_LOADED_KEY, () => false, ) /** * Initial request of the user identity for plugin initialization. * Only call this method when `sanctum.client.initialRequest` is false. */ async function init() { if (isIdentityLoaded.value) { return } isIdentityLoaded.value = true await refreshIdentity() await nuxtApp.callHook('sanctum:init') } /** * Fetches the user object from the API and sets it to the current state */ async function refreshIdentity() { user.value = await client(config.endpoints.user!) await nuxtApp.callHook('sanctum:refresh') } /** * Calls the login endpoint and sets the user object to the current state * * @param credentials Credentials to pass to the login endpoint * @param fetchIdentity Determines whether user identity should be fetched on successful response * @param options Additional fetch options */ async function login(credentials: Record, fetchIdentity: boolean = true, options: SanctumFetchOptions = {}): Promise { const currentRoute = useRoute() const currentPath = trimTrailingSlash(currentRoute.path) if (isAuthenticated.value) { if (!config.redirectIfAuthenticated) { throw new Error('User is already authenticated') } if ( config.redirect.onLogin === false || config.redirect.onLogin === currentPath ) { return } if (config.redirect.onLogin === undefined) { throw new Error('`sanctum.redirect.onLogin` is not defined') } const redirectUrl = config.redirect.onLogin as string await nuxtApp.callHook('sanctum:redirect', redirectUrl) await nuxtApp.runWithContext(async () => await navigateTo(redirectUrl)) } if (config.endpoints.login === undefined) { throw new Error('`sanctum.endpoints.login` is not defined') } const fetchOptions = { method: 'post', ...options, body: credentials, } const response = await client( config.endpoints.login, fetchOptions as SanctumFetchOptions<'json', unknown>, ) if (config.mode === 'token') { if (appConfig.tokenStorage === undefined) { throw new Error('`sanctum.tokenStorage` is not defined in app.config.ts') } if (response.token === undefined) { throw new Error('Token was not returned from the API') } await appConfig.tokenStorage.set(nuxtApp, response.token) } if (fetchIdentity) { await refreshIdentity() } await nuxtApp.callHook('sanctum:login') if (config.redirect.keepRequestedRoute) { const requestedRoute = currentRoute.query.redirect as string | undefined if (requestedRoute && requestedRoute !== currentPath) { await nuxtApp.callHook('sanctum:redirect', requestedRoute) await nuxtApp.runWithContext(async () => await navigateTo(requestedRoute)) return response } } if ( config.redirect.onLogin === false || currentRoute.path === config.redirect.onLogin ) { return response } if (config.redirect.onLogin === undefined) { throw new Error('`sanctum.redirect.onLogin` is not defined') } const redirectUrl = config.redirect.onLogin as string await nuxtApp.callHook('sanctum:redirect', redirectUrl) await nuxtApp.runWithContext(async () => await navigateTo(redirectUrl)) return response } /** * Calls the logout endpoint and clears the user object * @param options Additional fetch options */ async function logout(options: SanctumFetchOptions = {}): Promise { if (!isAuthenticated.value) { throw new Error('User is not authenticated') } const currentRoute = useRoute() const currentPath = trimTrailingSlash(currentRoute.path) if (config.endpoints.logout === undefined) { throw new Error('`sanctum.endpoints.logout` is not defined') } const fetchOptions = { method: 'post', ...options, } await client(config.endpoints.logout, fetchOptions) user.value = null await nuxtApp.callHook('sanctum:logout') if (config.mode === 'token') { await appConfig.tokenStorage!.set(nuxtApp, undefined) } if ( config.redirect.onLogout === false || currentPath === config.redirect.onLogout ) { return } if (config.redirect.onLogout === undefined) { throw new Error('`sanctum.redirect.onLogout` is not defined') } const redirectUrl = config.redirect.onLogout as string await nuxtApp.callHook('sanctum:redirect', redirectUrl) await nuxtApp.runWithContext(async () => await navigateTo(redirectUrl)) } return { user, isAuthenticated, init, login, logout, refreshIdentity, } as SanctumAuth } ================================================ FILE: src/runtime/composables/useSanctumClient.ts ================================================ import type { SanctumFetch } from '../types/fetch' import { useNuxtApp } from '#app' export const useSanctumClient = (): SanctumFetch => { const { $sanctumClient } = useNuxtApp() return $sanctumClient as SanctumFetch } ================================================ FILE: src/runtime/composables/useSanctumConfig.ts ================================================ import type { PublicModuleOptions, ModuleOptions } from '../types/options' import { useRuntimeConfig } from '#imports' import { isServerRuntime } from '../utils/runtime' export const useSanctumConfig = (): PublicModuleOptions | ModuleOptions => { const config = useRuntimeConfig() if (isServerRuntime()) { return config.sanctum as ModuleOptions } return config.public.sanctum as PublicModuleOptions } ================================================ FILE: src/runtime/composables/useSanctumFetch.ts ================================================ import { type UseFetchOptions, useFetch } from '#app' import { toRaw, toValue, type MaybeRefOrGetter } from 'vue' import { useSanctumClient } from '../composables/useSanctumClient' import type { SanctumFetchResponse } from '../types/fetch' export function useSanctumFetch( url: MaybeRefOrGetter, options?: UseFetchOptions, ): SanctumFetchResponse { const client = useSanctumClient() as typeof $fetch const key = options?.key ?? JSON.stringify([toRaw(toValue(url)), toRaw(toValue(options))]) const params = { ...options, key, $fetch: client } as UseFetchOptions // @ts-expect-error unable to satisfy params return useFetch(url, params) } ================================================ FILE: src/runtime/composables/useSanctumTokenStorage.ts ================================================ import type { TokenStorage } from '../types/config' import { updateAppConfig } from '#app' export const useSanctumTokenStorage = (storage: TokenStorage): void => { updateAppConfig({ sanctum: { tokenStorage: storage, }, }) } ================================================ FILE: src/runtime/composables/useSanctumUser.ts ================================================ import type { Ref } from 'vue' import { useSanctumConfig } from './useSanctumConfig' import { useState } from '#app' /** * Returns current authenticated user information. * @returns Reference to the user state as T. */ export const useSanctumUser = (): Ref => { const options = useSanctumConfig() return useState(options.userStateKey, () => null) } ================================================ FILE: src/runtime/httpFactory.ts ================================================ import type { FetchContext, FetchOptions } from 'ofetch' import type { ConsolaInstance } from 'consola' import { useSanctumConfig } from './composables/useSanctumConfig' import type { SanctumInterceptor } from './types/config' import { interceptors } from './interceptors' import { determineCredentialsMode } from './utils/credentials' import type { SanctumFetch } from './types/fetch' import type { NuxtApp } from '#app' /** * Returns a tuple of request and response interceptors. */ function useClientInterceptors(): [ SanctumInterceptor[], SanctumInterceptor[], SanctumInterceptor[], ] { const [request, response, responseError] = [ [...interceptors.request], [...interceptors.response], [...interceptors.responseError], ] return [request, response, responseError] } /** * Creates a custom OFetch instance with interceptors and Laravel-specific options. * * @param nuxtApp Nuxt application instance * @param logger Module logger instance */ export function createHttpClient(nuxtApp: NuxtApp, logger: ConsolaInstance): SanctumFetch { const options = useSanctumConfig() const [ requestInterceptors, responseInterceptors, responseErrorInterceptors, ] = useClientInterceptors() const httpOptions: FetchOptions = { baseURL: options.baseUrl, credentials: determineCredentialsMode(), redirect: 'manual', retry: options.client.retry === true ? 1 : options.client.retry, // false or number async onRequest(context: FetchContext): Promise { for (const interceptor of requestInterceptors) { await nuxtApp.runWithContext(async () => { await interceptor(nuxtApp, context, logger) }) } await nuxtApp.callHook('sanctum:request', nuxtApp, context, logger) }, async onResponse(context: FetchContext): Promise { for (const interceptor of responseInterceptors) { await nuxtApp.runWithContext(async () => { await interceptor(nuxtApp, context, logger) }) } await nuxtApp.callHook('sanctum:response', nuxtApp, context, logger) }, async onRequestError(context: FetchContext): Promise { await nuxtApp.callHook('sanctum:error:request', context) }, async onResponseError(context): Promise { for (const interceptor of responseErrorInterceptors) { await nuxtApp.runWithContext(async () => { await interceptor(nuxtApp, context, logger) }) } await nuxtApp.callHook('sanctum:error:response', context) }, } return $fetch.create(httpOptions) as SanctumFetch } ================================================ FILE: src/runtime/interceptors/index.ts ================================================ import type { SanctumInterceptor } from '../types/config' import { setRequestParams } from './request/params' import { setStatefulParams } from './request/stateful' import { setTokenHeader } from './request/token' import { logRequestHeaders } from './request/logging' import { proxyResponseHeaders } from './response/proxy' import { validateResponseHeaders } from './response/validation' import { logResponseHeaders } from './response/logging' import { handleResponseError } from './response/errorHandler' const [request, response, responseError] = [ [ setRequestParams, setStatefulParams, setTokenHeader, logRequestHeaders, ] as SanctumInterceptor[], [ proxyResponseHeaders, validateResponseHeaders, logResponseHeaders, ] as SanctumInterceptor[], [ handleResponseError, ] as SanctumInterceptor[], ] export const interceptors = { request, response, responseError, } ================================================ FILE: src/runtime/interceptors/request/logging.ts ================================================ import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import type { NuxtApp } from '#app' /** * Logs information about the request before it is sent to the API. * @param app Nuxt application instance * @param ctx Fetch context * @param logger Module logger instance */ export async function logRequestHeaders( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ): Promise { logger.trace( `Request headers for "${ctx.request.toString()}"`, ctx.options.headers instanceof Headers ? Object.fromEntries(ctx.options.headers.entries()) : ctx.options.headers, ) } ================================================ FILE: src/runtime/interceptors/request/params.ts ================================================ import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import type { NuxtApp } from '#app' const ACCEPT_HEADER = 'Accept' /** * Modify request before sending it to the Laravel API * @param app Nuxt application instance * @param ctx Fetch context * @param logger Module logger instance */ export async function setRequestParams( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ): Promise { const method = ctx.options.method?.toLowerCase() ?? 'get' if (!ctx.options.headers?.has(ACCEPT_HEADER)) { ctx.options.headers.set(ACCEPT_HEADER, 'application/json') logger.debug(`[request] added default ${ACCEPT_HEADER} header`) } // https://laravel.com/docs/10.x/routing#form-method-spoofing if (method === 'put' && ctx.options.body instanceof FormData) { ctx.options.method = 'POST' ctx.options.body.append('_method', 'PUT') logger.debug('[request] changed PUT to POST method for FormData compatibility') } } ================================================ FILE: src/runtime/interceptors/request/stateful.ts ================================================ import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import { useSanctumConfig } from '../../composables/useSanctumConfig' import type { PublicModuleOptions } from '../../types/options' import { useCookie, useRequestHeaders, useRequestURL, refreshCookie, type NuxtApp } from '#app' import { isServerRuntime } from '../../utils/runtime' const SECURE_METHODS = new Set(['post', 'delete', 'put', 'patch']) const COOKIE_OPTIONS: { readonly: true, watch: false } = { readonly: true, watch: false } /** * Pass all cookies, headers and referrer from the client to the API * @param headers Headers collection to extend * @param config Module configuration * @param logger Logger instance */ function useClientHeaders( headers: Headers, config: PublicModuleOptions, logger: ConsolaInstance, ): void { const clientHeaders = useRequestHeaders(['cookie', 'user-agent']) const origin = config.origin ?? useRequestURL().origin const headersToAdd = { Referer: origin, Origin: origin, ...(clientHeaders.cookie && { Cookie: clientHeaders.cookie }), ...(clientHeaders['user-agent'] && { 'User-Agent': clientHeaders['user-agent'] }), } for (const [key, value] of Object.entries(headersToAdd)) { headers.set(key, value) } logger.debug( '[request] added client headers to server request', Object.keys(headersToAdd), ) } /** * Request a new CSRF cookie from the API * @param config Module configuration * @param logger Logger instance */ async function initCsrfCookie( config: PublicModuleOptions, logger: ConsolaInstance, ): Promise { if (config.endpoints.csrf === undefined) { throw new Error('`sanctum.endpoints.csrf` is not defined') } await $fetch(config.endpoints.csrf, { baseURL: config.baseUrl, credentials: 'include', }) logger.debug('[request] CSRF cookie has been initialized') } /** * Add CSRF token to the headers collection to pass from the client to the API * @param headers Headers collection to extend * @param config Module configuration * @param logger Logger instance */ async function useCsrfHeader( headers: Headers, config: PublicModuleOptions, logger: ConsolaInstance, ): Promise { if (config.csrf.cookie === undefined) { throw new Error('`sanctum.csrf.cookie` is not defined') } if (config.csrf.header === undefined) { throw new Error('`sanctum.csrf.header` is not defined') } const csrfToken = useCookie(config.csrf.cookie, COOKIE_OPTIONS) if (!csrfToken.value) { await initCsrfCookie(config, logger) refreshCookie(config.csrf.cookie) } if (!csrfToken.value) { logger.warn(`${config.csrf.cookie} cookie is missing, unable to set ${config.csrf.header} header`) return } headers.set(config.csrf.header, csrfToken.value) logger.debug(`[request] added ${config.csrf.header} header`) } /** * Handle cookies and headers for the request * @param app Nuxt application instance * @param ctx Fetch context * @param logger Module logger instance */ export async function setStatefulParams( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ): Promise { const config = useSanctumConfig() if (config.mode !== 'cookie') { return } const method = ctx.options.method?.toLowerCase() ?? 'get' if (isServerRuntime()) { useClientHeaders( ctx.options.headers, config, logger, ) } if (SECURE_METHODS.has(method)) { await useCsrfHeader( ctx.options.headers, config, logger, ) } } ================================================ FILE: src/runtime/interceptors/request/token.ts ================================================ import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import { useSanctumAppConfig } from '../../composables/useSanctumAppConfig' import { useSanctumConfig } from '../../composables/useSanctumConfig' import type { NuxtApp } from '#app' const AUTHORIZATION_HEADER = 'Authorization' /** * Sets the authentication Bearer token in the request header * @param app Nuxt application instance * @param ctx Fetch context * @param logger Module logger instance */ export async function setTokenHeader( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ): Promise { const config = useSanctumConfig() if (config.mode !== 'token') { return } const appConfig = useSanctumAppConfig() if (appConfig.tokenStorage === undefined) { throw new Error('`sanctum.tokenStorage` is not defined in app.config.ts') } const token = await appConfig.tokenStorage.get(app) if (!token) { logger.debug('[request] authentication token is not set in the storage') return } const bearerToken = `Bearer ${token}` ctx.options.headers.set(AUTHORIZATION_HEADER, bearerToken) logger.debug(`[request] added ${AUTHORIZATION_HEADER} token header`) } ================================================ FILE: src/runtime/interceptors/response/errorHandler.ts ================================================ import type { FetchContext, FetchResponse } from 'ofetch' import type { ConsolaInstance } from 'consola' import { useSanctumConfig } from '../../composables/useSanctumConfig' import { useSanctumUser } from '../../composables/useSanctumUser' import { navigateTo } from '#app' import type { NuxtApp } from '#app' import { isServerRuntime } from '../../utils/runtime' /** * Handles error responses from the API. * * @param nuxtApp Nuxt application current instance. * @param context OFetch request/response context. * @param logger Module logger instance. */ export async function handleResponseError( nuxtApp: NuxtApp, context: FetchContext, logger: ConsolaInstance, ): Promise { const options = useSanctumConfig() const user = useSanctumUser() const response = context.response as FetchResponse if (response.status === 419) { logger.warn('CSRF token mismatch, check your API configuration') return } if (response.status === 401) { if (user.value !== null) { logger.warn('User session is not set in API or expired, resetting identity') user.value = null } if ( isServerRuntime() === false && options.redirectIfUnauthenticated && options.redirect.onAuthOnly ) { const redirectUrl = options.redirect.onAuthOnly await nuxtApp.callHook('sanctum:redirect', redirectUrl) await nuxtApp.runWithContext(async () => await navigateTo(redirectUrl)) } } } ================================================ FILE: src/runtime/interceptors/response/logging.ts ================================================ import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import type { NuxtApp } from '#app' /** * Logs information about the API response before it is sent to the client. * @param app Nuxt application instance * @param ctx Fetch context * @param logger Module logger instance */ export async function logResponseHeaders( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ): Promise { logger.trace( `Response headers for "${ctx.request.toString()}"`, ctx.response ? Object.fromEntries(ctx.response.headers.entries()) : {}, ) } ================================================ FILE: src/runtime/interceptors/response/proxy.ts ================================================ import type { OutgoingHttpHeaders } from 'node:http' import { getResponseHeaders, setResponseHeaders, splitCookiesString } from 'h3' import type { H3Event, TypedHeaders } from 'h3' import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import { useSanctumConfig } from '../../composables/useSanctumConfig' import { navigateTo, useRequestEvent } from '#app' import type { NuxtApp } from '#app' import { isServerRuntime } from '../../utils/runtime' const ServerCookieName = 'set-cookie' /** * Append server response headers to the client response * @param app Nuxt application instance * @param ctx Fetch context * @param logger Module logger instance */ function appendServerResponseHeaders( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ): void { const event = useRequestEvent(app) if (event === undefined) { logger.debug(`[response] no event to pass cookies to the client [${ctx.request}]`) return } const eventHeaders = getResponseHeaders(event) const cookiesFromEvent = extractCookiesFromEventHeaders(eventHeaders) const cookiesFromResponse = extractCookiesFromResponse(ctx, logger) const cookiesMap = createCookiesMap(cookiesFromEvent, cookiesFromResponse) writeCookiesToEventResponse(event, eventHeaders, cookiesMap) logger.debug( `[response] pass cookies from server to client response`, Array.from(cookiesMap.keys()), ) } /** * Extract cookies from the current H3 event headers * @param headers HTTP headers collection */ function extractCookiesFromEventHeaders(headers: OutgoingHttpHeaders): string[] { const cookieHeader = headers[ServerCookieName] ?? [] if (Array.isArray(cookieHeader)) { return cookieHeader } return [cookieHeader] } /** * Extract cookies from the remote API response headers * @param ctx Remote API fetch context * @param logger Module logger instance */ function extractCookiesFromResponse(ctx: FetchContext, logger: ConsolaInstance): string[] { const cookieHeader = ctx.response!.headers.get(ServerCookieName) if (cookieHeader === null) { logger.debug(`[response] no cookies to pass to the client [${ctx.request}]`) return [] } return splitCookiesString(cookieHeader) } /** * Create a map of cookies to deduplicate them * @param cookieCollections Arrays of cookies to merge */ function createCookiesMap(...cookieCollections: string[][]) { const cookiesMap = new Map() for (const cookies of cookieCollections) { for (const cookie of cookies) { const cookieName = cookie.split('=')[0] if (cookieName === undefined) { continue } cookiesMap.set(cookieName, cookie) } } return cookiesMap } /** * Write cookies to the event response headers, keeping the original headers * @param event H3 event instance * @param headers HTTP headers collection * @param cookiesMap Cookies map */ function writeCookiesToEventResponse(event: H3Event, headers: OutgoingHttpHeaders, cookiesMap: Map) { const mergedHeaders = { ...headers, [ServerCookieName]: Array.from(cookiesMap.values()), } as TypedHeaders setResponseHeaders(event, mergedHeaders) } /** * Pass all cookies from the API to the client on SSR response * @param app Nuxt application instance * @param ctx Fetch context * @param logger Module logger instance */ export async function proxyResponseHeaders( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ): Promise { const config = useSanctumConfig() if (config.mode !== 'cookie') { return } if (ctx.response === undefined) { logger.debug('[response] no response to process') return } if (isServerRuntime()) { appendServerResponseHeaders(app, ctx, logger) } // follow redirects on a client if (ctx.response.redirected) { const redirectUrl = ctx.response!.url await app.callHook('sanctum:redirect', redirectUrl) await app.runWithContext(async () => await navigateTo(redirectUrl)) } } ================================================ FILE: src/runtime/interceptors/response/validation.ts ================================================ import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import type { PublicModuleOptions } from '../../types/options' import { useRequestURL } from '#app' import type { NuxtApp } from '#app' import { isServerRuntime } from '../../utils/runtime' import { useSanctumConfig } from '../../composables/useSanctumConfig' type HeaderValidator = (headers: Headers, config: PublicModuleOptions, logger: ConsolaInstance) => void /** * Checks if the `set-cookie` header is present in the response headers. * @param headers The response headers * @param config Module options * @param logger Logger instance */ const validateCookieHeader: HeaderValidator = ( headers: Headers, config: PublicModuleOptions, logger: ConsolaInstance, ): void => { if (config.mode == 'token') { return } if (!headers.has('set-cookie')) { logger.warn('[response] `set-cookie` header is missing, CSRF token will not be set') } } /** * Checks if the `content-type` header is present and valid in the response headers. * @param headers The response headers * @param config Module options * @param logger Logger instance */ const validateContentTypeHeader: HeaderValidator = ( headers: Headers, config: PublicModuleOptions, logger: ConsolaInstance, ): void => { const contentType = headers.get('content-type') if (!contentType) { logger.warn('[response] "content-type" header is missing') return } if (!contentType.includes('application/json')) { logger.debug(`[response] 'content-type' is present in response but different (expected: application/json, got: ${contentType})`) } } /** * Checks if the `access-control-allow-credentials` header is present in the response headers. * @param headers The response headers * @param config Module options * @param logger Logger instance */ const validateCredentialsHeader: HeaderValidator = ( headers: Headers, config: PublicModuleOptions, logger: ConsolaInstance, ): void => { if (config.mode == 'token') { return } const allowCredentials = headers.get('access-control-allow-credentials') if (!allowCredentials || allowCredentials !== 'true') { logger.warn(`[response] 'access-control-allow-credentials' header is missing or invalid (expected: true, got: ${allowCredentials})`) } } /** * Checks if the `access-control-allow-origin` header is the same as the current origin. * @param headers The response headers * @param config Module options * @param logger Logger instance */ const validateOriginHeader: HeaderValidator = ( headers: Headers, config: PublicModuleOptions, logger: ConsolaInstance, ): void => { const allowOrigin = headers.get('access-control-allow-origin') const currentOrigin = config?.origin ?? useRequestURL().origin if (!allowOrigin || !allowOrigin.includes(currentOrigin)) { logger.warn(`[response] 'access-control-allow-origin' header is missing or invalid (expected: ${currentOrigin}, got: ${allowOrigin})`) } } const validators: HeaderValidator[] = [ validateCookieHeader, validateContentTypeHeader, validateCredentialsHeader, validateOriginHeader, ] /** * Validate response headers and log warnings if any are missing or invalid. * @param app Nuxt application instance * @param ctx Fetch context * @param logger Module logger instance */ export async function validateResponseHeaders( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ): Promise { if (isServerRuntime() === false) { logger.debug('[response] skipping headers validation on CSR') return } const config = useSanctumConfig() const headers = ctx.response?.headers if (!headers) { logger.warn('[response] no headers returned from API') return } for (const validator of validators) { validator(headers, config, logger) } } ================================================ FILE: src/runtime/middleware/sanctum.auth.ts ================================================ import type { RouteLocationAsPathGeneric } from 'vue-router' import { useSanctumConfig } from '../composables/useSanctumConfig' import { trimTrailingSlash } from '../utils/formatter' import { defineNuxtRouteMiddleware, navigateTo, createError } from '#app' import { isUserSessionActive } from '../utils/session' export default defineNuxtRouteMiddleware(async (to) => { const options = useSanctumConfig() if (await isUserSessionActive()) { return } const endpoint = options.redirect.onAuthOnly if (endpoint === undefined) { throw new Error('`sanctum.redirect.onAuthOnly` is not defined') } if (endpoint === false) { throw createError({ statusCode: 403 }) } const redirect: RouteLocationAsPathGeneric = { path: endpoint } if (options.redirect.keepRequestedRoute) { redirect.query = { redirect: trimTrailingSlash(to.fullPath) } } return navigateTo(redirect, { replace: true }) }) ================================================ FILE: src/runtime/middleware/sanctum.global.ts ================================================ import type { RouteLocationAsPathGeneric } from 'vue-router' import { useSanctumConfig } from '../composables/useSanctumConfig' import { trimTrailingSlash } from '../utils/formatter' import { defineNuxtRouteMiddleware, navigateTo } from '#app' import { isUserSessionActive } from '../utils/session' export default defineNuxtRouteMiddleware(async (to) => { const options = useSanctumConfig() const [homePage, loginPage] = [ options.redirect.onGuestOnly, options.redirect.onAuthOnly, ] if (homePage === undefined || homePage === false) { throw new Error( 'You must define onGuestOnly route when using global middleware.', ) } if (loginPage === undefined || loginPage === false) { throw new Error( 'You must define onAuthOnly route when using global middleware.', ) } if ( options.globalMiddleware.allow404WithoutAuth && to.matched.length === 0 ) { return } if (to.meta.sanctum?.excluded === true) { return } const isPageForGuestsOnly = trimTrailingSlash(to.path) === loginPage || to.meta.sanctum?.guestOnly === true if (await isUserSessionActive()) { if (isPageForGuestsOnly) { return navigateTo(homePage, { replace: true }) } return } if (isPageForGuestsOnly) { return } const redirect: RouteLocationAsPathGeneric = { path: loginPage } if (options.redirect.keepRequestedRoute) { redirect.query = { redirect: trimTrailingSlash(to.fullPath) } } return navigateTo(redirect, { replace: true }) }) ================================================ FILE: src/runtime/middleware/sanctum.guest.ts ================================================ import { useSanctumAuth } from '../composables/useSanctumAuth' import { useSanctumConfig } from '../composables/useSanctumConfig' import { defineNuxtRouteMiddleware, navigateTo, createError } from '#app' export default defineNuxtRouteMiddleware(() => { const options = useSanctumConfig() const { isAuthenticated } = useSanctumAuth() if (!isAuthenticated.value) { return } const endpoint = options.redirect.onGuestOnly if (endpoint === undefined) { throw new Error('`sanctum.redirect.onGuestOnly` is not defined') } if (endpoint === false) { throw createError({ statusCode: 403 }) } return navigateTo(endpoint, { replace: true }) }) ================================================ FILE: src/runtime/plugin.ts ================================================ import type { $Fetch } from 'ofetch' import type { ConsolaInstance } from 'consola' import type { NuxtApp } from '#app' import type { PublicModuleOptions } from './types/options' import type { TokenStorage } from './types/config' import { IDENTITY_LOADED_KEY } from './utils/constants' import { createHttpClient } from './httpFactory' import { defineNuxtPlugin, useState } from '#app' import { useSanctumAppConfig } from './composables/useSanctumAppConfig' import { useSanctumConfig } from './composables/useSanctumConfig' import { useSanctumLogger } from './utils/logging' import { useSanctumTokenStorage } from './composables/useSanctumTokenStorage' import { useSanctumUser } from './composables/useSanctumUser' import { isServerRuntime } from './utils/runtime' async function resolveTokenStorage(nuxtApp: NuxtApp, logger: ConsolaInstance): Promise { let appConfig = useSanctumAppConfig() if (appConfig.tokenStorage) { return appConfig.tokenStorage } await nuxtApp.callHook('sanctum:storage:token') appConfig = await nuxtApp.runWithContext(() => useSanctumAppConfig()) if (appConfig.tokenStorage) { return appConfig.tokenStorage } logger.debug('Token storage is not defined, switch to default cookie storage') const defaultStorage = await import('./storages/cookieTokenStorage') return defaultStorage.cookieTokenStorage } async function initialIdentityLoad(nuxtApp: NuxtApp, client: $Fetch, options: PublicModuleOptions, logger: ConsolaInstance) { const identityFetchedOnInit = useState( IDENTITY_LOADED_KEY, () => false, ) if (identityFetchedOnInit.value) { return } const user = useSanctumUser() if (user.value !== null) { return } identityFetchedOnInit.value = true logger.debug('Fetching user identity on plugin initialization') if (!options.endpoints.user) { throw new Error('`sanctum.endpoints.user` is not defined') } try { const response = await client.raw( options.endpoints.user, { ignoreResponseError: true }, ) if (response.ok) { user.value = response._data return await nuxtApp.callHook('sanctum:init') } if ([401, 419].includes(response.status)) { logger.debug( 'User is not authenticated on plugin initialization, status:', response.status, ) } else { logger.error( 'Unable to load user identity from API', { status: response.status }, ) } } catch (err) { logger.error( 'An unexpected error occurred while fetching user identity', { reason: err }, ) } } export default defineNuxtPlugin({ name: 'nuxt-auth-sanctum', async setup(_nuxtApp) { const nuxtApp = _nuxtApp as NuxtApp const options = useSanctumConfig() const logger = useSanctumLogger(options.logLevel, isServerRuntime()) const client = createHttpClient(nuxtApp, logger) if (options.mode === 'token') { nuxtApp.hook( 'page:loading:start', async () => { const tokenStorage = await resolveTokenStorage(nuxtApp, logger) await nuxtApp.runWithContext(() => useSanctumTokenStorage(tokenStorage)) }, ) } if (options.client.initialRequest) { nuxtApp.hook( 'page:loading:start', async () => { await initialIdentityLoad(nuxtApp, client, options, logger) }, ) } return { provide: { sanctumClient: client, }, } }, }) ================================================ FILE: src/runtime/server/api/proxy.ts ================================================ import type { EventHandlerRequest, H3Event, HTTPMethod, RequestHeaders } from 'h3' import { appendResponseHeader, defineEventHandler, getQuery, getRequestHeader, getRequestHeaders, getRequestWebStream, readBody, setResponseStatus, } from 'h3' import { $fetch, type FetchContext, type FetchResponse } from 'ofetch' import { useSanctumLogger } from '../../utils/logging' import { determineCredentialsMode } from '../../utils/credentials' import { trimTrailingSlash } from '../../utils/formatter' import type { ModuleOptions } from '../../types/options' import { useRuntimeConfig } from '#imports' import type { ConsolaInstance } from 'consola' import { useNitroApp } from 'nitropack/runtime' import { isServerRuntime } from '../../utils/runtime' const METHODS_WITH_BODY: HTTPMethod[] = ['POST', 'PUT', 'PATCH', 'DELETE'] const REQUEST_HEADERS_TO_IGNORE = [ 'connection', 'upgrade', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailer', 'transfer-encoding', 'host', 'content-length', 'accept-encoding', ] const RESPONSE_HEADERS_TO_IGNORE = [ ...REQUEST_HEADERS_TO_IGNORE, 'content-encoding', ] export default defineEventHandler(async (event: H3Event) => { const config = useRuntimeConfig().sanctum as ModuleOptions const logger = useSanctumLogger(config.logLevel, isServerRuntime()) const endpoint = assembleEndpoint(event, config.serverProxy.baseUrl) logger.debug(`[sanctum] proxying request to ${endpoint}`) const response = await proxyRequest(event, endpoint, logger) prepareResponse(event, response) logger.debug(`[sanctum] proxying response from ${endpoint}`) return response._data }) function assembleEndpoint(event: H3Event, baseUrl: string): string { const base = trimTrailingSlash(baseUrl), path = trimTrailingSlash(event.context.params?._ || '') if (!base) { throw new Error('[sanctum] serverProxy.baseUrl is not configured') } if (path.length === 0) { return base } return `${base}/${path}` } function dropProxyHeaders(headers: RequestHeaders): RequestHeaders { const filteredHeaders = Object .entries(headers) .filter(([name]) => !REQUEST_HEADERS_TO_IGNORE.includes(name.toLowerCase())) return Object.fromEntries(filteredHeaders) } async function proxyRequest(event: H3Event, endpoint: string, logger: ConsolaInstance): Promise> { const nitroApp = useNitroApp() const method = event.method, query = getQuery(event), body = await getBody(event), headers = { accept: 'application/json', ...dropProxyHeaders(getRequestHeaders(event)), } return await $fetch.raw(endpoint, { method: method, query: query, body: body, credentials: determineCredentialsMode(), headers: headers, ignoreResponseError: true, async onRequest(context: FetchContext): Promise { await nitroApp.hooks.callHook('sanctum:proxy:request', context, logger) }, }) } async function getBody(event: H3Event) { if (!METHODS_WITH_BODY.includes(event.method)) { return Promise.resolve(undefined) } const contentLength = getRequestHeader(event, 'content-length') if (!contentLength || contentLength === '0') { return Promise.resolve(undefined) } const contentType = getRequestHeader(event, 'content-type') if (contentType?.includes('multipart/form-data')) { return getRequestWebStream(event) } return readBody(event) } function prepareResponse(event: H3Event, response: FetchResponse): void { response.headers.forEach((value, key) => { if (RESPONSE_HEADERS_TO_IGNORE.includes(key.toLowerCase())) { return } appendResponseHeader(event, key, value) }) setResponseStatus(event, response.status) } ================================================ FILE: src/runtime/server/augments.server.d.ts ================================================ declare module 'nitropack/types' { interface NitroRuntimeHooks { /** * Triggers on every proxy client request. */ 'sanctum:proxy:request': (ctx: FetchContext, logger: ConsolaInstance) => void } } ================================================ FILE: src/runtime/storages/cookieTokenStorage.ts ================================================ import { unref } from 'vue' import type { TokenStorage } from '../types/config' import { useCookie, useRequestURL } from '#app' import type { NuxtApp } from '#app' const cookieTokenKey = 'sanctum.token.cookie' /** * Token storage using a secure cookie for HTTPS and plain cookie for HTTP. * Works with both CSR/SSR modes. */ export const cookieTokenStorage: TokenStorage = { async get(app: NuxtApp) { return app.runWithContext(() => { const cookie = useCookie(cookieTokenKey, { readonly: true, watch: false }) return unref(cookie.value) ?? undefined }) }, async set(app: NuxtApp, token?: string) { await app.runWithContext(() => { const isSecure = useRequestURL().protocol.startsWith('https') const cookie = useCookie(cookieTokenKey, { secure: isSecure }) cookie.value = token }) }, } ================================================ FILE: src/runtime/types/config.ts ================================================ import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import type { NuxtApp } from '#app' /** * Handlers to work with authentication token. */ export interface TokenStorage { /** * Function to load a token from the storage. */ get: (app: NuxtApp) => Promise /** * Function to save a token to the storage. */ set: (app: NuxtApp, token?: string) => Promise } /** * Interceptor definition type. */ export type SanctumInterceptor = ( app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance, ) => Promise /** * Sanctum configuration for the application side with user-defined handlers. */ export interface SanctumAppConfig { /** * Token storage handlers to be used by the client. */ tokenStorage?: TokenStorage } ================================================ FILE: src/runtime/types/fetch.ts ================================================ import type { MappedResponseType, FetchRequest, Fetch, FetchOptions, FetchResponse, ResponseType, $Fetch, FetchError } from 'ofetch' import type { AsyncData, KeysOf, PickFrom } from '#app/composables/asyncData' export type SanctumFetchOptions = Omit< FetchOptions, 'baseUrl' | 'credentials' | 'redirect' | 'retry' | 'onRequest' | 'onRequestError' | 'onResponse' | 'onResponseError' > export interface SanctumFetch extends $Fetch { (request: FetchRequest, options?: SanctumFetchOptions): Promise> raw(request: FetchRequest, options?: SanctumFetchOptions): Promise>> native: Fetch } export type SanctumFetchResponse = AsyncData> | undefined, FetchError | undefined> ================================================ FILE: src/runtime/types/meta.ts ================================================ /** * Page meta information to be used by the global middleware. */ export interface SanctumGlobalMiddlewarePageMeta { /** * Determines whether the page should be excluded from middleware checks. */ excluded?: boolean /** * Determines whether the page should be accessible only by unauthenticated users. */ guestOnly?: boolean } ================================================ FILE: src/runtime/types/options.ts ================================================ /** * Definition of Laravel Sanctum endpoints to be used by the client. */ export interface SanctumEndpoints { /** * The endpoint to request a new CSRF token. * @default '/sanctum/csrf-cookie' */ csrf: string /** * The endpoint to send user credentials to authenticate. * @default '/login' */ login: string /** * The endpoint to destroy current user session. * @default '/logout' */ logout: string /** * The endpoint to fetch current user data. * @default '/api/user' */ user: string } /** * CSRF token-specific options. */ export interface CsrfOptions { /** * Name of the CSRF cookie to extract from server response. * @default 'XSRF-TOKEN' */ cookie: string /** * Name of the CSRF header to pass from client to server. * @default 'X-XSRF-TOKEN' */ header: string } /** * OFetch client-specific options. */ export interface ClientOptions { /** * The number of times to retry a request when it fails. * @default false */ retry: number | boolean /** * Determines whether to request the user identity on plugin initialization. * @default true */ initialRequest: boolean } /** * Behavior of the plugin redirects when the user is authenticated or not. */ export interface RedirectOptions { /** * Determines whether to keep the requested route when redirecting after login. * @default false */ keepRequestedRoute: boolean /** * Route to redirect to when the user is authenticated. * If set to false, do nothing. * @default '/' */ onLogin: string | false /** * Route to redirect to when the user is not authenticated. * If set to false, do nothing. * @default '/' */ onLogout: string | false /** * Route to redirect to when the user has to be authenticated. * If set to false, the plugin will throw a 403 error. * @default '/login' */ onAuthOnly: string | false /** * Route to redirect to when a user has to be a guest. * If set to false, the plugin will throw a 403 error. * @default '/' */ onGuestOnly: string | false } /** * Configuration of the global application-wide middleware. */ export interface GlobalMiddlewareOptions { /** * Determines whether the global middleware is enabled. * @default false */ enabled: boolean /** * Determines whether the global middleware is prepended. * @default false */ prepend: boolean /** * Determines whether to allow 404 pages without authentication. * @default true */ allow404WithoutAuth: boolean } /** * Server API proxy configuration to handle Laravel requests on SSR-side first. */ export interface ServerProxy { /** * Determines whether the server side proxy is enabled. * @default false */ enabled: boolean /** * Nuxt server route to catch all requests. This route will receive any nested path as well, * for instance: `/api/sanctum` will also catch `/api/sanctum/login` and `/api/sanctum/user/info`. * @default '/api/sanctum' */ route: string /** * The base URL of the Laravel API. * @default 'http://localhost:80' */ baseUrl: string } /** * Options to be passed to the plugin. */ export interface PublicModuleOptions { /** * The base URL of the Laravel API. * @default 'http://localhost:80' */ baseUrl: string /** * The mode to use for authentication. * @default 'cookie' */ mode: 'cookie' | 'token' /** * The URL of the current application to use in the Referrer header. (Optional) * @default useRequestURL().origin */ origin?: string /** * The key to use to store the user identity in the `useState` variable. * @default 'sanctum.user.identity' */ userStateKey: string /** * Determine whether to redirect the user if it is already authenticated on a login attempt. * @default false */ redirectIfAuthenticated: boolean /** * Determine whether to redirect when the user got unauthenticated on any API request. * @default false */ redirectIfUnauthenticated: boolean /** * Laravel Sanctum endpoints to be used by the client. */ endpoints: Partial /** * CSRF token-specific options. */ csrf: Partial /** * OFetch client-specific options. */ client: Partial /** * Behavior of the plugin redirects when the user is authenticated or not. */ redirect: Partial /** * Behavior of the global middleware. */ globalMiddleware: Partial /** * The log level to use for the logger. * * 0: Fatal and Error * 1: Warnings * 2: Normal logs * 3: Informational logs * 4: Debug logs * 5: Trace logs * * More details at https://github.com/unjs/consola?tab=readme-ov-file#log-level * @default 3 */ logLevel: number /** * Determines whether to append the plugin to the Nuxt application. * By default, Nuxt prepends the plugin to load it before the application modules. * @default false * @see https://nuxt.com/docs/api/kit/plugins#options */ appendPlugin: boolean } export interface ModuleOptions extends PublicModuleOptions { /** * Server API proxy configuration to handle Laravel requests on SSR-side first. */ serverProxy: ServerProxy } ================================================ FILE: src/runtime/utils/constants.ts ================================================ export const IDENTITY_LOADED_KEY = 'sanctum.user.loaded' ================================================ FILE: src/runtime/utils/credentials.ts ================================================ /** * Determines the credentials mode for the fetch request. */ export function determineCredentialsMode() { // Fix for Cloudflare workers - https://github.com/cloudflare/workers-sdk/issues/2514 const isCredentialsSupported = 'credentials' in Request.prototype if (!isCredentialsSupported) { return undefined } return 'include' } ================================================ FILE: src/runtime/utils/formatter.ts ================================================ /** * Removes the trailing slash from a path if it exists. * @param path The URL path. */ export function trimTrailingSlash(path: string): string { if (path.length > 1 && path.slice(-1) === '/') { return path.slice(0, -1) } return path } ================================================ FILE: src/runtime/utils/logging.ts ================================================ import { createConsola } from 'consola' import type { ConsolaInstance } from 'consola' const LOGGER_NAME = 'nuxt-auth-sanctum' export function useSanctumLogger(logLevel: number, server: boolean): ConsolaInstance { const envSuffix = server ? 'ssr' : 'csr' const loggerName = LOGGER_NAME + ':' + envSuffix return createConsola({ level: logLevel }).withTag(loggerName) } ================================================ FILE: src/runtime/utils/runtime.ts ================================================ export const isServerRuntime = () => import.meta.server ================================================ FILE: src/runtime/utils/session.ts ================================================ import { useCookie, useNuxtApp } from '#app' import { unref } from 'vue' import { useSanctumLogger } from '../utils/logging' import { useSanctumConfig } from '../composables/useSanctumConfig' import { useSanctumAppConfig } from '../composables/useSanctumAppConfig' import { useSanctumAuth } from '../composables/useSanctumAuth' import { isServerRuntime } from './runtime' /** * Validates existence of the current user session details */ export async function isUserSessionActive(): Promise { const nuxtApp = useNuxtApp() const options = useSanctumConfig() const appConfig = useSanctumAppConfig() const { isAuthenticated, refreshIdentity } = useSanctumAuth() if (isAuthenticated.value === false) { return false } const logger = useSanctumLogger(options.logLevel, isServerRuntime()) if (options.mode == 'cookie') { const csrfToken = unref( useCookie( options.csrf.cookie!, { readonly: true, watch: false }, ), ) if (!csrfToken) { try { logger.debug('[sanctum] csrf cookie is outdated, refreshing identity') await refreshIdentity() } catch { logger.debug('[sanctum] unable to refresh identity on route change') } } } if (options.mode == 'token') { const token = await appConfig.tokenStorage!.get(nuxtApp) if (!token) { try { logger.debug('[sanctum] csrf token is outdated, refreshing identity') await refreshIdentity() } catch { logger.debug('[sanctum] unable to refresh identity on route change') } } } return isAuthenticated.value } ================================================ FILE: src/templates.ts ================================================ import { addTypeTemplate } from '@nuxt/kit' import type { Resolver } from '@nuxt/kit' import { relative, resolve } from 'pathe' export const registerTypeTemplates = (resolver: Resolver) => { addTypeTemplate({ filename: 'types/sanctum.d.ts', getContents: ({ nuxt }) => { const { buildDir } = nuxt.options const getRelativePath = (path: string) => relative(resolve(buildDir, './types'), resolver.resolve(path)) return `// Generated by nuxt-auth-sanctum module import type { NuxtApp } from '#app' import type { HookResult } from '@nuxt/schema' import type { FetchContext } from 'ofetch' import type { ConsolaInstance } from 'consola' import type { SanctumAppConfig } from '${getRelativePath('./runtime/types/config.ts')}'; import type { SanctumGlobalMiddlewarePageMeta } from '${getRelativePath('./runtime/types/meta.ts')}'; declare module '@nuxt/schema' { interface AppConfigInput { sanctum?: SanctumAppConfig } } declare module '#app' { interface PageMeta { /** * Sanctum global middleware page configuration. */ sanctum?: Partial } interface RuntimeNuxtHooks { /** * Triggers when receiving an error response. */ 'sanctum:error:response': (context: FetchContext) => HookResult /** * Triggers when receiving an error on fetch request. */ 'sanctum:error:request': (context: FetchContext) => HookResult /** * Triggers when the user has been redirected. */ 'sanctum:redirect': (path: string) => HookResult /** * Triggers when an initial user identity request has been made. */ 'sanctum:init': () => HookResult /** * Triggers when user identity has been refreshed. */ 'sanctum:refresh': () => HookResult /** * Triggers when user successfully logs in. */ 'sanctum:login': () => HookResult /** * Triggers when user successfully logs out. */ 'sanctum:logout': () => HookResult /** * Triggers on every client request. */ 'sanctum:request': (app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance) => HookResult /** * Triggers on every server response. */ 'sanctum:response': (app: NuxtApp, ctx: FetchContext, logger: ConsolaInstance) => HookResult /** * Triggers to register custom token storage. * Call useSanctumTokenStorage() inside this hook. */ 'sanctum:storage:token': () => HookResult } } declare module 'nitropack/types' { interface NitroRuntimeHooks { /** * Triggers on every proxy client request. */ 'sanctum:proxy:request': (ctx: FetchContext, logger: ConsolaInstance) => void } } export {}` }, }) } ================================================ FILE: test/helpers/constants.ts ================================================ export const TEST_CONFIG = { BASE_URL: 'http://localhost:80', CUSTOM_BASE_URL: 'http://remote-host.dev', CUSTOM_ORIGIN: 'http://custom-origin.dev', CSRF_COOKIE_NAME: 'cookie_name', CSRF_HEADER_NAME: 'header_name', CSRF_ENDPOINT: '/api/token', AUTH_TOKEN: 'abc123token', CSRF_TOKEN: 'csrf-token-cookie-value', REDIRECT_LOGIN: '/login', REDIRECT_HOME: '/', } export const HTTP_METHODS_REQUIRING_CSRF = ['POST', 'PUT', 'PATCH', 'DELETE'] export const COMMON_HEADERS = { CONTENT_TYPE: 'content-type', AUTHORIZATION: 'authorization', ACCEPT: 'accept', COOKIE: 'cookie', USER_AGENT: 'user-agent', ORIGIN: 'origin', REFERER: 'referer', SET_COOKIE: 'set-cookie', ACCESS_CONTROL_ALLOW_CREDENTIALS: 'access-control-allow-credentials', ACCESS_CONTROL_ALLOW_ORIGIN: 'access-control-allow-origin', } ================================================ FILE: test/helpers/mocks.ts ================================================ import type { NuxtApp } from '#app' import type { ConsolaInstance } from 'consola' import { vi } from 'vitest' export function createMock(mock: object = {}): T { return mock as T } export function createAppMock(mock: object = {}): NuxtApp { const resolved = { callHook: vi.fn(), runWithContext: vi.fn().mockImplementation((fn: () => void) => { fn() }), ...mock, } return createMock(resolved) } export function createLoggerMock(mock: object = {}): ConsolaInstance { const resolved = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), trace: vi.fn(), ...mock, } return createMock(resolved) } ================================================ FILE: test/unit/config.test.ts ================================================ import { describe, it, expect } from 'vitest' import { defu } from 'defu' import { defaultModuleOptions } from '../../src/config' import type { ModuleOptions } from '../../src/runtime/types/options' describe('default config', () => { it('should have correct default values for all config keys', () => { expect(defaultModuleOptions).toStrictEqual({ baseUrl: 'http://localhost:80', mode: 'cookie', userStateKey: 'sanctum.user.identity', redirectIfAuthenticated: false, redirectIfUnauthenticated: false, endpoints: { csrf: '/sanctum/csrf-cookie', login: '/login', logout: '/logout', user: '/api/user', }, csrf: { cookie: 'XSRF-TOKEN', header: 'X-XSRF-TOKEN', }, client: { retry: false, initialRequest: true, }, redirect: { keepRequestedRoute: false, onLogin: '/', onLogout: '/', onAuthOnly: '/login', onGuestOnly: '/', }, globalMiddleware: { enabled: false, prepend: false, allow404WithoutAuth: true, }, logLevel: 3, appendPlugin: false, serverProxy: { enabled: false, route: '/api/sanctum', baseUrl: 'http://localhost:80', }, }) }) }) describe('config merging', () => { it('should merge user config with defaults', () => { const userConfig = { baseUrl: 'https://api.example.com', mode: 'token' as const, endpoints: { user: '/api/v2/user', }, redirect: { onLogin: '/dashboard', }, } const merged = defu(userConfig, defaultModuleOptions) as ModuleOptions expect(merged.baseUrl).toBe('https://api.example.com') expect(merged.mode).toBe('token') expect(merged.logLevel).toBe(defaultModuleOptions.logLevel) expect(merged.endpoints!.csrf).toBe(defaultModuleOptions.endpoints!.csrf) expect(merged.endpoints!.user).toBe('/api/v2/user') expect(merged.redirect!.onLogin).toBe('/dashboard') expect(merged.redirect!.onLogout).toBe(defaultModuleOptions.redirect!.onLogout) }) }) describe('type safety', () => { it('should accept valid mode values', () => { const cookieConfig: ModuleOptions = { ...defaultModuleOptions, mode: 'cookie', } expect(cookieConfig.mode).toBe('cookie') const tokenConfig: ModuleOptions = { ...defaultModuleOptions, mode: 'token', } expect(tokenConfig.mode).toBe('token') }) it('should allow boolean redirect options', () => { const config: ModuleOptions = { ...defaultModuleOptions, redirect: { onLogin: false, onLogout: false, onAuthOnly: false, onGuestOnly: false, keepRequestedRoute: true, }, } expect(config.redirect!.onLogin).toBe(false) expect(config.redirect!.onLogout).toBe(false) }) }) ================================================ FILE: test/unit/constants.test.ts ================================================ import { describe, expect, it } from 'vitest' import { IDENTITY_LOADED_KEY } from '../../src/runtime/utils/constants' describe('constants', () => { it('has expected identity key', () => { expect(IDENTITY_LOADED_KEY).toBe('sanctum.user.loaded') }) }) ================================================ FILE: test/unit/credentials.test.ts ================================================ import { describe, expect, it, beforeEach, afterEach } from 'vitest' import { determineCredentialsMode } from '../../src/runtime/utils/credentials' describe('credentials', () => { let deleteCredentials: () => boolean beforeEach(() => { deleteCredentials = () => delete (global.Request.prototype as unknown as Record)['credentials'] }) afterEach(() => { Object.defineProperty(global.Request.prototype, 'credentials', { value: { toString: () => 'include' }, writable: true, configurable: true, }) }) it('returns "include" when credentials are supported', () => { expect(determineCredentialsMode()).toBe('include') }) it('returns "undefined" when credentials are not supported', () => { deleteCredentials() expect(determineCredentialsMode()).toBeUndefined() }) }) ================================================ FILE: test/unit/formatter.test.ts ================================================ import { describe, expect, it } from 'vitest' import { trimTrailingSlash } from '../../src/runtime/utils/formatter' describe('formatters', () => { describe('trimTrailingSlash', () => { it('does not remove root slash', () => { const value = '/' expect(trimTrailingSlash(value)).toBe(value) }) it('returns unchanged string if correct', () => { const value = '/home' expect(trimTrailingSlash(value)).toBe(value) }) it('returns path without trailing slash', () => { const value = '/home/' expect(trimTrailingSlash(value)).toBe('/home') }) it('removes only one slash', () => { const value = '/home//' expect(trimTrailingSlash(value)).toBe('/home/') }) }) }) ================================================ FILE: test/unit/interceptors/request/logging.test.ts ================================================ import type { FetchContext } from 'ofetch' import { describe, it, expect, vi, beforeEach } from 'vitest' import { logRequestHeaders } from '../../../../src/runtime/interceptors/request/logging' import { createAppMock, createLoggerMock, createMock } from '../../../helpers/mocks' describe('request interceptors', () => { beforeEach(() => { vi.clearAllMocks() }) describe('logRequestHeaders', () => { it('writes trace log on request with plain headers object', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ request: new URL('https://example.com/api/user'), options: { headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer token123', }, }, }) await logRequestHeaders(mockApp, ctx, mockLogger) expect(mockLogger.trace).toHaveBeenCalledWith( expect.stringContaining('Request headers for "https://example.com/api/user"'), { 'Content-Type': 'application/json', 'Authorization': 'Bearer token123', }, ) }) it('writes trace log on request with Headers instance', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ request: new URL('https://example.com/api/user'), options: { headers: new Headers({ 'Content-Type': 'application/json', 'Authorization': 'Bearer token123', }), }, }) await logRequestHeaders(mockApp, ctx, mockLogger) expect(mockLogger.trace).toHaveBeenCalledWith( expect.stringContaining('Request headers for "https://example.com/api/user"'), { 'content-type': 'application/json', 'authorization': 'Bearer token123', }, ) }) }) }) ================================================ FILE: test/unit/interceptors/request/params.test.ts ================================================ import { describe, it, expect, vi, beforeEach } from 'vitest' import { setRequestParams } from '../../../../src/runtime/interceptors/request/params' import { createAppMock, createLoggerMock, createMock } from '../../../helpers/mocks' import type { FetchContext } from 'ofetch' describe('request interceptors', () => { beforeEach(() => { vi.clearAllMocks() }) describe('setRequestParams', () => { it('sets application/json accept header if not set', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: 'GET', headers: new Headers(), }, }) await setRequestParams(mockApp, ctx, mockLogger) expect(ctx.options.headers?.get('Accept')).toBe('application/json') expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining('[request] added default Accept header')) }) it('does not override existing accept header', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: 'GET', headers: new Headers({ Accept: 'application/xml' }), }, }) await setRequestParams(mockApp, ctx, mockLogger) expect(ctx.options.headers?.get('Accept')).toBe('application/xml') expect(mockLogger.debug).not.toHaveBeenCalled() }) it('updates FormData body on PUT requests', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const formData = new FormData() formData.append('name', 'test') const ctx = createMock({ options: { method: 'PUT', headers: new Headers(), body: formData, }, }) await setRequestParams(mockApp, ctx, mockLogger) expect(ctx.options.method).toBe('POST') expect((ctx.options.body as FormData)!.get('_method')).toBe('PUT') expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining('[request] changed PUT to POST method')) }) it('does not modify non-FormData PUT body', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: 'PUT', headers: new Headers({ Accept: 'application/json' }), body: { name: 'test' }, }, }) await setRequestParams(mockApp, ctx, mockLogger) expect(ctx.options.method).toBe('PUT') expect(mockLogger.debug).not.toHaveBeenCalled() }) }) }) ================================================ FILE: test/unit/interceptors/request/stateful.test.ts ================================================ import { beforeEach, describe, expect, it, vi } from 'vitest' import { setStatefulParams } from '../../../../src/runtime/interceptors/request/stateful' import { createAppMock, createLoggerMock, createMock } from '../../../helpers/mocks' import { TEST_CONFIG, HTTP_METHODS_REQUIRING_CSRF } from '../../../helpers/constants' import type { FetchContext } from 'ofetch' const { useSanctumConfigMock, isServerRuntimeMock, useRequestHeadersMock, useRequestURLMock, useCookieMock, refreshCookieMock, fetchMock, } = vi.hoisted(() => { return { useSanctumConfigMock: vi.fn(), isServerRuntimeMock: vi.fn(), useRequestHeadersMock: vi.fn(), useRequestURLMock: vi.fn(), useCookieMock: vi.fn(), refreshCookieMock: vi.fn(), fetchMock: vi.fn(), } }) vi.mock( '../../../../src/runtime/composables/useSanctumConfig', () => ({ useSanctumConfig: useSanctumConfigMock }), ) vi.mock( '../../../../src/runtime/utils/runtime', () => ({ isServerRuntime: isServerRuntimeMock.mockReturnValue(true) }), ) vi.mock( '#app', () => ({ useRequestHeaders: useRequestHeadersMock, useRequestURL: useRequestURLMock, useCookie: useCookieMock, refreshCookie: refreshCookieMock, }), ) vi.stubGlobal('$fetch', fetchMock) describe('request interceptors', () => { beforeEach(() => { vi.clearAllMocks() vi.restoreAllMocks() }) describe('setStatefulParams', () => { it('no-op when token mode is enabled', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'token' }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({}) await setStatefulParams(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).not.toHaveBeenCalled() expect(mockLogger.debug).not.toHaveBeenCalled() }) it('skips client headers in CSR', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) isServerRuntimeMock.mockReturnValue(false) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: undefined }, }) await setStatefulParams(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).toHaveBeenCalled() expect(mockLogger.debug).not.toHaveBeenCalled() }) it('appends client headers in SSR [cookie, user-agent, origin]', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie', origin: TEST_CONFIG.CUSTOM_ORIGIN, }) isServerRuntimeMock.mockReturnValue(true) useRequestHeadersMock.mockReturnValue({ 'cookie': 'random-cookie=value', 'user-agent': 'random-user-agent', }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: undefined, headers: new Headers(), }, }) await setStatefulParams(mockApp, ctx, mockLogger) expect(ctx.options.headers).toStrictEqual( new Headers({ 'Referer': TEST_CONFIG.CUSTOM_ORIGIN, 'Origin': TEST_CONFIG.CUSTOM_ORIGIN, 'Cookie': 'random-cookie=value', 'User-Agent': 'random-user-agent', }), ) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestHeadersMock).toHaveBeenCalledWith(['cookie', 'user-agent']) expect(useRequestURLMock).not.toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith( '[request] added client headers to server request', ['Referer', 'Origin', 'Cookie', 'User-Agent'], ) }) it('appends request origin if not provided', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) isServerRuntimeMock.mockReturnValue(true) useRequestHeadersMock.mockReturnValue({ 'cookie': 'random-cookie=value', 'user-agent': 'random-user-agent', }) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: undefined, headers: new Headers(), }, }) await setStatefulParams(mockApp, ctx, mockLogger) expect(ctx.options.headers).toStrictEqual( new Headers({ 'Referer': TEST_CONFIG.CUSTOM_ORIGIN, 'Origin': TEST_CONFIG.CUSTOM_ORIGIN, 'Cookie': 'random-cookie=value', 'User-Agent': 'random-user-agent', }), ) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestHeadersMock).toHaveBeenCalledWith(['cookie', 'user-agent']) expect(useRequestURLMock).toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith( '[request] added client headers to server request', ['Referer', 'Origin', 'Cookie', 'User-Agent'], ) }) describe.each(HTTP_METHODS_REQUIRING_CSRF)('appends CSRF for %s method', (method) => { it(`appends CSRF for ${method} method`, async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie', csrf: { cookie: TEST_CONFIG.CSRF_COOKIE_NAME, header: TEST_CONFIG.CSRF_HEADER_NAME, }, }) isServerRuntimeMock.mockReturnValue(false) useCookieMock.mockReturnValue({ value: TEST_CONFIG.CSRF_TOKEN }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: method.toLowerCase() as 'post' | 'put' | 'patch' | 'delete', headers: new Headers(), }, }) await setStatefulParams(mockApp, ctx, mockLogger) expect(ctx.options.headers).toStrictEqual( new Headers({ [TEST_CONFIG.CSRF_HEADER_NAME]: TEST_CONFIG.CSRF_TOKEN, }), ) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestHeadersMock).not.toHaveBeenCalled() expect(useRequestURLMock).not.toHaveBeenCalled() expect(useCookieMock).toHaveBeenCalledWith( TEST_CONFIG.CSRF_COOKIE_NAME, { readonly: true, watch: false }, ) expect(mockLogger.debug).toHaveBeenCalledWith('[request] added header_name header') }) }) it('throws error if cookie name is undefined', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie', csrf: { cookie: undefined, header: undefined, }, }) isServerRuntimeMock.mockReturnValue(false) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: 'post', headers: new Headers(), }, }) await expect(setStatefulParams(mockApp, ctx, mockLogger)) .rejects .toThrow('`sanctum.csrf.cookie` is not defined') expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestHeadersMock).not.toHaveBeenCalled() expect(useRequestURLMock).not.toHaveBeenCalled() expect(mockLogger.debug).not.toHaveBeenCalled() }) it('throws error if cookie header name is undefined', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie', csrf: { cookie: TEST_CONFIG.CSRF_COOKIE_NAME, header: undefined, }, }) isServerRuntimeMock.mockReturnValue(false) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: 'post', headers: new Headers(), }, }) await expect(setStatefulParams(mockApp, ctx, mockLogger)) .rejects .toThrow('`sanctum.csrf.header` is not defined') expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestHeadersMock).not.toHaveBeenCalled() expect(useRequestURLMock).not.toHaveBeenCalled() expect(mockLogger.debug).not.toHaveBeenCalled() }) it('requests CSRF token if not fetched yet', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie', baseUrl: TEST_CONFIG.CUSTOM_BASE_URL, csrf: { cookie: TEST_CONFIG.CSRF_COOKIE_NAME, header: TEST_CONFIG.CSRF_HEADER_NAME, }, endpoints: { csrf: TEST_CONFIG.CSRF_ENDPOINT, }, }) isServerRuntimeMock.mockReturnValue(false) const csrfToken = { value: undefined as string | undefined } useCookieMock.mockImplementation(() => csrfToken) fetchMock.mockImplementation(() => csrfToken.value = 'token-value') const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: 'delete', headers: new Headers(), }, }) await setStatefulParams(mockApp, ctx, mockLogger) expect(ctx.options.headers).toStrictEqual( new Headers({ [TEST_CONFIG.CSRF_HEADER_NAME]: 'token-value', }), ) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestHeadersMock).not.toHaveBeenCalled() expect(useRequestURLMock).not.toHaveBeenCalled() expect(useCookieMock).toHaveBeenCalledWith( TEST_CONFIG.CSRF_COOKIE_NAME, { readonly: true, watch: false }, ) expect(fetchMock).toHaveBeenCalledWith(TEST_CONFIG.CSRF_ENDPOINT, { baseURL: TEST_CONFIG.CUSTOM_BASE_URL, credentials: 'include', }) expect(mockLogger.debug).toHaveBeenCalledWith('[request] CSRF cookie has been initialized') expect(refreshCookieMock).toHaveBeenCalledWith(TEST_CONFIG.CSRF_COOKIE_NAME) expect(mockLogger.warn).not.toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith('[request] added header_name header') }) it('throws error if csrf endpoint is undefined', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie', csrf: { cookie: TEST_CONFIG.CSRF_COOKIE_NAME, header: TEST_CONFIG.CSRF_HEADER_NAME, }, endpoints: { csrf: undefined, }, }) isServerRuntimeMock.mockReturnValue(false) useCookieMock.mockReturnValue({ value: undefined }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: 'delete', headers: new Headers(), }, }) await expect(setStatefulParams(mockApp, ctx, mockLogger)) .rejects .toThrow('`sanctum.endpoints.csrf` is not defined') expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestHeadersMock).not.toHaveBeenCalled() expect(useRequestURLMock).not.toHaveBeenCalled() expect(useCookieMock).toHaveBeenCalledWith( TEST_CONFIG.CSRF_COOKIE_NAME, { readonly: true, watch: false }, ) expect(refreshCookieMock).not.toHaveBeenCalled() expect(mockLogger.debug).not.toHaveBeenCalled() }) it('writes warning if no CSRF token returned from API', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie', baseUrl: TEST_CONFIG.CUSTOM_BASE_URL, csrf: { cookie: TEST_CONFIG.CSRF_COOKIE_NAME, header: TEST_CONFIG.CSRF_HEADER_NAME, }, endpoints: { csrf: TEST_CONFIG.CSRF_ENDPOINT, }, }) isServerRuntimeMock.mockReturnValue(false) useCookieMock.mockReturnValue({ value: undefined }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { method: 'delete', headers: new Headers(), }, }) await setStatefulParams(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestHeadersMock).not.toHaveBeenCalled() expect(useRequestURLMock).not.toHaveBeenCalled() expect(useCookieMock).toHaveBeenCalledWith( TEST_CONFIG.CSRF_COOKIE_NAME, { readonly: true, watch: false }, ) expect(fetchMock).toHaveBeenCalledWith(TEST_CONFIG.CSRF_ENDPOINT, { baseURL: TEST_CONFIG.CUSTOM_BASE_URL, credentials: 'include', }) expect(mockLogger.debug).toHaveBeenCalledWith('[request] CSRF cookie has been initialized') expect(refreshCookieMock).toHaveBeenCalledWith(TEST_CONFIG.CSRF_COOKIE_NAME) expect(mockLogger.warn).toHaveBeenCalledWith('cookie_name cookie is missing, unable to set header_name header') expect(mockLogger.debug).not.toHaveBeenCalledWith('[request] added header_name header') }) }) }) ================================================ FILE: test/unit/interceptors/request/token.test.ts ================================================ import { describe, it, expect, vi, beforeEach } from 'vitest' import { setTokenHeader } from '../../../../src/runtime/interceptors/request/token' import { createAppMock, createLoggerMock, createMock } from '../../../helpers/mocks' import { TEST_CONFIG } from '../../../helpers/constants' import type { FetchContext } from 'ofetch' const { useSanctumConfigMock, useSanctumAppConfigMock, } = vi.hoisted(() => { return { useSanctumConfigMock: vi.fn().mockReturnValue({ mode: 'token' }), useSanctumAppConfigMock: vi.fn().mockReturnValue({ tokenStorage: undefined }), } }) vi.mock( '../../../../src/runtime/composables/useSanctumConfig', () => ({ useSanctumConfig: useSanctumConfigMock }), ) vi.mock( '../../../../src/runtime/composables/useSanctumAppConfig', () => ({ useSanctumAppConfig: useSanctumAppConfigMock }), ) describe('request interceptors', () => { beforeEach(() => { vi.clearAllMocks() }) describe('setTokenHeader', () => { it('skips when mode is not token', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { headers: new Headers(), }, }) await setTokenHeader(mockApp, ctx, mockLogger) expect(mockLogger.debug).not.toHaveBeenCalled() expect(ctx.options.headers?.get('Authorization')).toBeNull() }) it('throws when tokenStorage not defined', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'token' }) useSanctumAppConfigMock.mockReturnValue({ tokenStorage: undefined }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { headers: new Headers(), }, }) await expect(setTokenHeader(mockApp, ctx, mockLogger)) .rejects .toThrow('`sanctum.tokenStorage` is not defined in app.config.ts') }) it('skips when token is not in storage', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'token' }) useSanctumAppConfigMock.mockReturnValue({ tokenStorage: { get: vi.fn().mockResolvedValue(null) } as unknown, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { headers: new Headers(), }, }) await setTokenHeader(mockApp, ctx, mockLogger) expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining('[request] authentication token is not set')) expect(ctx.options.headers?.get('Authorization')).toBeNull() }) it('sets Bearer token in authorization header', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'token' }) useSanctumAppConfigMock.mockReturnValue({ tokenStorage: { get: vi.fn().mockResolvedValue(TEST_CONFIG.AUTH_TOKEN) } as unknown, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ options: { headers: new Headers(), }, }) await setTokenHeader(mockApp, ctx, mockLogger) expect(ctx.options.headers?.get('Authorization')).toBe(`Bearer ${TEST_CONFIG.AUTH_TOKEN}`) expect(mockLogger.debug).toHaveBeenCalledWith(expect.stringContaining('[request] added Authorization token header')) }) }) }) ================================================ FILE: test/unit/interceptors/response/errorHandler.test.ts ================================================ import { describe, it, expect, vi, beforeEach } from 'vitest' import { handleResponseError } from '../../../../src/runtime/interceptors/response/errorHandler' import { createAppMock, createLoggerMock, createMock } from '../../../helpers/mocks' import { TEST_CONFIG } from '../../../helpers/constants' import type { FetchContext } from 'ofetch' const { useSanctumConfigMock, useSanctumUserMock, navigateToMock, isServerRuntimeMock, } = vi.hoisted(() => { return { useSanctumConfigMock: vi.fn(), useSanctumUserMock: vi.fn(), navigateToMock: vi.fn(), isServerRuntimeMock: vi.fn(), } }) vi.mock( '../../../../src/runtime/composables/useSanctumConfig', () => ({ useSanctumConfig: useSanctumConfigMock }), ) vi.mock( '../../../../src/runtime/composables/useSanctumUser', () => ({ useSanctumUser: useSanctumUserMock }), ) vi.mock( '../../../../src/runtime/utils/runtime', () => ({ isServerRuntime: isServerRuntimeMock.mockReturnValue(true) }), ) vi.mock('#app', () => ({ navigateTo: navigateToMock })) describe('response interceptors', () => { beforeEach(() => { vi.clearAllMocks() }) describe('handleResponseError', () => { it('writes warning on 419 response', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { status: 419 } }) await handleResponseError(mockApp, ctx, mockLogger) expect(mockLogger.warn).toHaveBeenCalledWith('CSRF token mismatch, check your API configuration') }) it('resets identity on 401 response', async () => { const mockUser = { value: { id: 1, name: 'John' } } useSanctumUserMock.mockReturnValue(mockUser) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { status: 401 } }) await handleResponseError(mockApp, ctx, mockLogger) expect(mockUser.value).toBeNull() expect(mockLogger.warn).toHaveBeenCalledWith('User session is not set in API or expired, resetting identity') }) it('no-op on 401 response if not authenticated', async () => { const mockUser = { value: null } useSanctumUserMock.mockReturnValue(mockUser) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { status: 401 } }) await handleResponseError(mockApp, ctx, mockLogger) expect(mockUser.value).toBeNull() expect(mockLogger.warn).not.toHaveBeenCalled() }) it('redirects on client for 401 with redirect enabled', async () => { useSanctumConfigMock.mockReturnValue({ redirectIfUnauthenticated: true, redirect: { onAuthOnly: TEST_CONFIG.REDIRECT_LOGIN }, }) useSanctumUserMock.mockReturnValue({ value: { id: 1 } }) isServerRuntimeMock.mockReturnValue(false) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { status: 401 } }) await handleResponseError(mockApp, ctx, mockLogger) expect(mockApp.callHook).toHaveBeenCalledWith('sanctum:redirect', TEST_CONFIG.REDIRECT_LOGIN) expect(navigateToMock).toHaveBeenCalledWith(TEST_CONFIG.REDIRECT_LOGIN) }) it('skips redirect on server for 401', async () => { useSanctumConfigMock.mockReturnValue({ redirectIfUnauthenticated: true, redirect: { onAuthOnly: TEST_CONFIG.REDIRECT_LOGIN }, }) useSanctumUserMock.mockReturnValue({ value: { id: 1 } }) isServerRuntimeMock.mockReturnValue(true) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { status: 401 } }) await handleResponseError(mockApp, ctx, mockLogger) expect(mockLogger.warn).toHaveBeenCalledWith('User session is not set in API or expired, resetting identity') expect(mockApp.callHook).not.toHaveBeenCalled() expect(navigateToMock).not.toHaveBeenCalled() }) it('no action for other status codes', async () => { const mockUser = { value: { id: 1 } } useSanctumUserMock.mockReturnValue(mockUser) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { status: 500 } }) await handleResponseError(mockApp, ctx, mockLogger) expect(mockLogger.warn).not.toHaveBeenCalled() expect(mockUser.value).not.toBeNull() }) }) }) ================================================ FILE: test/unit/interceptors/response/logging.test.ts ================================================ import type { FetchContext } from 'ofetch' import { describe, expect, it, vi, beforeEach } from 'vitest' import { logResponseHeaders } from '../../../../src/runtime/interceptors/response/logging' import { createAppMock, createLoggerMock, createMock } from '../../../helpers/mocks' describe('response interceptors', () => { beforeEach(() => { vi.clearAllMocks() }) describe('logResponseHeaders', () => { it('writes headers when response is not empty', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ request: new URL('https://example.com/api/user'), response: { headers: new Headers({ 'Content-Type': 'application/json', 'X-Custom-Header': 'value123', }), }, }) await logResponseHeaders(mockApp, ctx, mockLogger) expect(mockLogger.trace).toHaveBeenCalledWith( expect.stringContaining('Response headers for "https://example.com/api/user"'), { 'content-type': 'application/json', 'x-custom-header': 'value123', }, ) }) it('writes empty object when response is empty', async () => { const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ request: new URL('https://example.com/api/user'), response: undefined, }) await logResponseHeaders(mockApp, ctx, mockLogger) expect(mockLogger.trace).toHaveBeenCalledWith( expect.stringContaining('Response headers for "https://example.com/api/user"'), {}, ) }) }) }) ================================================ FILE: test/unit/interceptors/response/proxy.test.ts ================================================ import { beforeEach, describe, expect, it, vi } from 'vitest' import { proxyResponseHeaders } from '../../../../src/runtime/interceptors/response/proxy' import { createAppMock, createLoggerMock, createMock } from '../../../helpers/mocks' import type { FetchContext } from 'ofetch' import type { H3Event, EventHandlerRequest } from 'h3' const { useSanctumConfigMock, useRequestEventMock, navigateToMock, isServerRuntimeMock, } = vi.hoisted(() => { return { useSanctumConfigMock: vi.fn(), useRequestEventMock: vi.fn(), navigateToMock: vi.fn(), isServerRuntimeMock: vi.fn(), } }) vi.mock( '../../../../src/runtime/composables/useSanctumConfig', () => ({ useSanctumConfig: useSanctumConfigMock }), ) vi.mock( '../../../../src/runtime/utils/runtime', () => ({ isServerRuntime: isServerRuntimeMock.mockReturnValue(true) }), ) vi.mock( '#app', () => ({ useRequestEvent: useRequestEventMock, navigateTo: navigateToMock, }), ) describe('response interceptors', () => { beforeEach(() => { vi.clearAllMocks() }) describe('proxyResponseHeaders', () => { it('no-op when token mode enabled', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'token' }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({}) await proxyResponseHeaders(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).not.toHaveBeenCalled() expect(useRequestEventMock).not.toHaveBeenCalled() expect(navigateToMock).not.toHaveBeenCalled() }) it('writes warning on empty response', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({}) await proxyResponseHeaders(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).not.toHaveBeenCalled() expect(useRequestEventMock).not.toHaveBeenCalled() expect(navigateToMock).not.toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith('[response] no response to process') }) it('follows redirects from response', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) isServerRuntimeMock.mockReturnValue(false) const mockApp = createAppMock() const mockLogger = createLoggerMock() const redirectUrl = 'http://redirect.dev' const ctx = createMock({ response: { redirected: true, url: redirectUrl, }, }) await proxyResponseHeaders(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestEventMock).not.toHaveBeenCalled() expect(navigateToMock).toHaveBeenCalledWith(redirectUrl) expect(mockApp.callHook).toHaveBeenCalledWith('sanctum:redirect', redirectUrl) expect(mockApp.runWithContext).toHaveBeenCalled() expect(mockLogger.debug).not.toHaveBeenCalled() }) it('appends headers in SSR mode', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) isServerRuntimeMock.mockReturnValue(true) const mockEvent = createMock>({ node: { res: { getHeaders: vi .fn() .mockReturnValue({ 'set-cookie': 'event-cookie-name=value-one', }), setHeader: vi.fn(), }, }, }) useRequestEventMock.mockReturnValue(mockEvent) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ 'set-cookie': 'response-cookie-name=value-two', }), }, }) await proxyResponseHeaders(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestEventMock).toHaveBeenCalledWith(mockApp) expect(navigateToMock).not.toHaveBeenCalled() expect(mockApp.callHook).not.toHaveBeenCalledWith() expect(mockApp.runWithContext).not.toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith( '[response] pass cookies from server to client response', [ 'event-cookie-name', 'response-cookie-name', ], ) expect(mockEvent.node.res.setHeader).toHaveBeenCalledWith( 'set-cookie', [ 'event-cookie-name=value-one', 'response-cookie-name=value-two', ], ) }) it('writes warning when request event is not defined', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) isServerRuntimeMock.mockReturnValue(true) useRequestEventMock.mockReturnValue(undefined) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ request: 'http://requested-url.dev', response: {}, }) await proxyResponseHeaders(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestEventMock).toHaveBeenCalledWith(mockApp) expect(navigateToMock).not.toHaveBeenCalled() expect(mockApp.callHook).not.toHaveBeenCalledWith() expect(mockApp.runWithContext).not.toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith(`[response] no event to pass cookies to the client [${ctx.request}]`) }) it('writes debug when no cookies returned from API', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) isServerRuntimeMock.mockReturnValue(true) const mockEvent = createMock>({ node: { res: { getHeaders: vi .fn() .mockReturnValue({ 'set-cookie': 'event-cookie-name=value-one', }), setHeader: vi.fn(), }, }, }) useRequestEventMock.mockReturnValue(mockEvent) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ request: 'http://requested-url.dev', response: { headers: new Headers({}), }, }) await proxyResponseHeaders(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestEventMock).toHaveBeenCalledWith(mockApp) expect(navigateToMock).not.toHaveBeenCalled() expect(mockApp.callHook).not.toHaveBeenCalledWith() expect(mockApp.runWithContext).not.toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith(`[response] no cookies to pass to the client [${ctx.request}]`) expect(mockLogger.debug).toHaveBeenCalledWith( '[response] pass cookies from server to client response', [ 'event-cookie-name', ], ) expect(mockEvent.node.res.setHeader).toHaveBeenCalledWith( 'set-cookie', [ 'event-cookie-name=value-one', ], ) }) it('deduplicates cookies in the response', async () => { useSanctumConfigMock.mockReturnValue({ mode: 'cookie' }) isServerRuntimeMock.mockReturnValue(true) const mockEvent = createMock>({ node: { res: { getHeaders: vi .fn() .mockReturnValue({ 'set-cookie': 'unique-cookie-name=value-one', }), setHeader: vi.fn(), }, }, }) useRequestEventMock.mockReturnValue(mockEvent) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ 'set-cookie': 'unique-cookie-name=value-two', }), }, }) await proxyResponseHeaders(mockApp, ctx, mockLogger) expect(isServerRuntimeMock).toHaveBeenCalled() expect(useRequestEventMock).toHaveBeenCalledWith(mockApp) expect(navigateToMock).not.toHaveBeenCalled() expect(mockApp.callHook).not.toHaveBeenCalledWith() expect(mockApp.runWithContext).not.toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith( '[response] pass cookies from server to client response', [ 'unique-cookie-name', ], ) expect(mockEvent.node.res.setHeader).toHaveBeenCalledWith( 'set-cookie', [ 'unique-cookie-name=value-two', ], ) }) }) }) ================================================ FILE: test/unit/interceptors/response/validation.test.ts ================================================ import { beforeEach, describe, expect, it, vi } from 'vitest' import { validateResponseHeaders } from '../../../../src/runtime/interceptors/response/validation' import { createAppMock, createLoggerMock, createMock } from '../../../helpers/mocks' import { TEST_CONFIG, COMMON_HEADERS } from '../../../helpers/constants' import type { FetchContext } from 'ofetch' const { isServerRuntimeMock, useRequestURLMock, useRuntimeConfigMock, } = vi.hoisted(() => { return { isServerRuntimeMock: vi.fn(), useRequestURLMock: vi.fn(), useRuntimeConfigMock: vi.fn(), } }) vi.mock( '../../../../src/runtime/utils/runtime', () => ({ isServerRuntime: isServerRuntimeMock }), ) vi.mock( '#app', () => ({ useRequestURL: useRequestURLMock }), ) vi.mock( '#imports', () => ({ useRuntimeConfig: useRuntimeConfigMock }), ) describe('response interceptors', () => { beforeEach(() => { vi.clearAllMocks() }) describe('validateResponseHeaders', () => { it('skips validation in CSR mode', async () => { isServerRuntimeMock.mockReturnValue(false) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({}) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(mockLogger.debug).toHaveBeenCalledWith('[response] skipping headers validation on CSR') }) it('writes warning log on empty headers', async () => { isServerRuntimeMock.mockReturnValue(true) useRuntimeConfigMock.mockReturnValue({ sanctum: {}, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({}) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(mockLogger.warn).toHaveBeenCalledWith('[response] no headers returned from API') }) it('passes validation when all headers are present', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: TEST_CONFIG.CUSTOM_ORIGIN, }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(mockLogger.warn).not.toHaveBeenCalledWith('[response] `set-cookie` header is missing, CSRF token will not be set') }) it('writes warning if cookie header is missing [cookie mode]', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: TEST_CONFIG.CUSTOM_ORIGIN, }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(mockLogger.warn).toHaveBeenCalledWith('[response] `set-cookie` header is missing, CSRF token will not be set') }) it('does not validate cookie header [token mode]', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'token', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: TEST_CONFIG.CUSTOM_ORIGIN, }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(mockLogger.warn).not.toHaveBeenCalled() }) it('writes warning if content-type header is missing', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: TEST_CONFIG.CUSTOM_ORIGIN, }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(mockLogger.warn).toHaveBeenCalledWith('[response] "content-type" header is missing') }) it('writes debug if content-type header is not JSON', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.CONTENT_TYPE]: 'unknown', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: TEST_CONFIG.CUSTOM_ORIGIN, }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(useRuntimeConfigMock).toHaveBeenCalled() expect(mockLogger.debug).toHaveBeenCalledWith(`[response] 'content-type' is present in response but different (expected: application/json, got: unknown)`) }) it('writes warning if credentials header is missing [cookie mode]', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: TEST_CONFIG.CUSTOM_ORIGIN, }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(useRuntimeConfigMock).toHaveBeenCalled() expect(mockLogger.warn).toHaveBeenCalledWith(`[response] 'access-control-allow-credentials' header is missing or invalid (expected: true, got: null)`) }) it('writes warning if credentials header is disabled [cookie mode]', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'false', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: TEST_CONFIG.CUSTOM_ORIGIN, }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(useRuntimeConfigMock).toHaveBeenCalled() expect(mockLogger.warn).toHaveBeenCalledWith(`[response] 'access-control-allow-credentials' header is missing or invalid (expected: true, got: false)`) }) it('skips validation of credentials header [token mode]', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'token', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: TEST_CONFIG.CUSTOM_ORIGIN, }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(useRuntimeConfigMock).toHaveBeenCalled() expect(mockLogger.warn).not.toHaveBeenCalled() }) it('writes warning if origin header is missing', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true', }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(useRuntimeConfigMock).toHaveBeenCalled() expect(mockLogger.warn).toHaveBeenCalledWith(`[response] 'access-control-allow-origin' header is missing or invalid (expected: ${TEST_CONFIG.CUSTOM_ORIGIN}, got: null)`) }) it('writes warning if origin header does not include request origin', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: 'http://another-host.dev', }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(useRuntimeConfigMock).toHaveBeenCalled() expect(mockLogger.warn).toHaveBeenCalledWith(`[response] 'access-control-allow-origin' header is missing or invalid (expected: ${TEST_CONFIG.CUSTOM_ORIGIN}, got: http://another-host.dev)`) }) it('writes warning if origin header does not include origin from config', async () => { isServerRuntimeMock.mockReturnValue(true) useRequestURLMock.mockReturnValue({ origin: TEST_CONFIG.CUSTOM_ORIGIN }) useRuntimeConfigMock.mockReturnValue({ sanctum: { mode: 'cookie', origin: 'http://sanctum-host.dev', }, public: { sanctum: { origin: 'http://client-sanctum-host.dev', }, }, }) const mockApp = createAppMock() const mockLogger = createLoggerMock() const ctx = createMock({ response: { headers: new Headers({ [COMMON_HEADERS.SET_COOKIE]: 'header_value', [COMMON_HEADERS.CONTENT_TYPE]: 'application/json', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_CREDENTIALS]: 'true', [COMMON_HEADERS.ACCESS_CONTROL_ALLOW_ORIGIN]: 'http://another-host.dev', }), }, }) await validateResponseHeaders(mockApp, ctx, mockLogger) expect(useRuntimeConfigMock).toHaveBeenCalled() expect(mockLogger.warn).toHaveBeenCalledWith(`[response] 'access-control-allow-origin' header is missing or invalid (expected: http://sanctum-host.dev, got: http://another-host.dev)`) }) }) }) ================================================ FILE: test/unit/logging.test.ts ================================================ import { describe, expect, it } from 'vitest' import { useSanctumLogger } from '../../src/runtime/utils/logging' describe('logging', () => { describe('useSanctumLogger', () => { it('returns a ConsolaInstance', () => { const logger = useSanctumLogger(3, false) expect(logger).toBeDefined() expect(typeof logger.info).toBe('function') expect(typeof logger.debug).toBe('function') expect(typeof logger.warn).toBe('function') expect(typeof logger.error).toBe('function') expect(typeof logger.trace).toBe('function') expect(typeof logger.log).toBe('function') }) it('applies log level correctly', () => { const logger0 = useSanctumLogger(0, false) const logger3 = useSanctumLogger(3, false) const logger5 = useSanctumLogger(5, false) expect(logger0.level).toBe(0) expect(logger3.level).toBe(3) expect(logger5.level).toBe(5) }) it('creates logger with ssr tag in SSR environment', () => { const logger = useSanctumLogger(3, true) const tag = logger.options?.defaults?.tag ?? '' expect(tag).toMatch('nuxt-auth-sanctum:ssr') }) it('creates logger with csr tag in CSR environment', () => { const logger = useSanctumLogger(3, false) const tag = logger.options?.defaults?.tag ?? '' expect(tag).toMatch('nuxt-auth-sanctum:csr') }) }) }) ================================================ FILE: test/unit/runtime.test.ts ================================================ import { describe, expect, it } from 'vitest' import { isServerRuntime } from '../../src/runtime/utils/runtime' describe('runtime', () => { describe('isServerRuntime', () => { it('returns import.meta.server value', () => { const isServer = import.meta.server expect(isServerRuntime()).toBe(isServer) }) }) }) ================================================ FILE: tsconfig.json ================================================ { "extends": "./.nuxt/tsconfig.json", "exclude": ["dist", "node_modules", "playground", "docs", "test/fixtures"] } ================================================ FILE: vitest.config.ts ================================================ import { defineConfig } from 'vitest/config' import { defineVitestProject } from '@nuxt/test-utils/config' export default defineConfig({ test: { hookTimeout: 10000, testTimeout: 5000, projects: [ { test: { name: 'unit', include: ['test/unit/**/*.{test,spec}.ts'], environment: 'node', testTimeout: 5000, hookTimeout: 10000, }, }, { test: { name: 'e2e', include: ['test/e2e/**/*.{test,spec}.ts'], environment: 'node', testTimeout: 30000, hookTimeout: 30000, }, }, await defineVitestProject({ test: { name: 'nuxt', include: ['test/nuxt/**/*.{test,spec}.ts'], environment: 'nuxt', testTimeout: 30000, hookTimeout: 30000, }, }), ], }, })