vue-auth3
vue-auth3 copied to clipboard
Not showing Previous/From Page when directly hitting a page that needs authorisation.
Hey there,
Thanks for building this this awesome plugin. My app needs to know the previous route before redirecting to the login page if unauthorised. I was thinking of storing this in a Store for safe keeping.
It seems, if I'm coming from a valid page, then hit a route with meta: {auth: true}, it redirects me and shows me the previous page fine. However if i directly hit that unauthorised page it comes back with an empty from, and also empty auth.redirect() object.
By directly I mean a new chrome tab - then hit https://domain/requires-auth, it doesn't show me the previous page, but if I'm in a https://domain/open-page, then click a link that takes me to https://domain/requires-auth it shows me the previous page.
This is the method I'm using to try and find the previous page:
router.beforeEach((to, from) => { console.log('from'); console.log(from); console.log(auth.redirect()) })
I hope this explanation makes sense. I must be doing something wrong?
Cheers
Potential solution
To solve the issue of not showing the previous route when directly accessing a page that requires authorization, we need to ensure that the previous route information is stored and retrieved correctly. The solution involves updating the route guard logic to store the previous route in local storage and retrieve it when necessary.
What is causing this bug?
The bug is caused by the fact that when a user directly accesses a protected page (e.g., by opening a new browser tab and navigating to a URL that requires authentication), there is no previous route information available. This results in an empty from object and an empty auth.redirect() object. The current implementation does not handle this scenario, leading to the issue described.
Code
Update src/shims-router.d.ts
Add a property for storing the previous route information in the RouteMeta interface.
import "vue-router"
import Roles from "./type/Roles"
declare module "vue-router" {
interface RouteMeta {
auth?:
| boolean
| string
| Roles[]
| {
roles?: Roles
redirect?:
| RouteLocationRaw
| ((to: RouteLocationNormalized) => RouteLocationRaw)
notFoundRedirect?:
| RouteLocationRaw
| ((to: RouteLocationNormalized) => RouteLocationRaw)
forbiddenRedirect?:
| RouteLocationRaw
| ((to: RouteLocationNormalized) => RouteLocationRaw)
rolesKey?: string
}
previousRoute?: string // Add this line to store the previous route information
}
}
Update src/utils/index.ts
Add utility functions for storing and retrieving the previous route information from local storage.
export function storePreviousRoute(route: string): void {
if (isLocalStorage()) {
localStorage.setItem('previousRoute', route);
}
}
export function getPreviousRoute(): string | null {
if (isLocalStorage()) {
return localStorage.getItem('previousRoute');
}
return null;
}
Update src/Auth.ts
Modify the authentication logic to store the previous route in local storage when redirecting to the login page.
this.options.plugins?.router?.beforeEach(async (to, from, next) => {
if (from.name) {
this.tPrev = from;
storePreviousRoute(from.fullPath);
} else {
const storedPrevRoute = getPreviousRoute();
this.tPrev = storedPrevRoute ? { fullPath: storedPrevRoute } : null;
}
this.tCurrent = to;
await syncStorage(this);
const authMeta = getAuthMeta(to);
processTransitionEach(this, to, authMeta, (redirect) => {
if (!redirect) {
next();
return;
}
next(redirect);
});
});
Clear the previous route on logout.
function logout(auth: Auth, redirect?: RouteLocationRaw) {
$cookie.remove(auth, auth.options.tokenImpersonateKey);
$cookie.remove(auth, auth.options.tokenDefaultKey);
$token.remove(auth, auth.options.tokenImpersonateKey);
$token.remove(auth, auth.options.tokenDefaultKey);
$token.remove(auth, auth.options.staySignedInKey);
$token.remove(auth, auth.options.userKey);
auth.state.loaded = true;
auth.state.authenticated = false;
auth.state.data = null;
localStorage.removeItem('previousRoute');
routerPush(auth, redirect);
}
Update src/index.ts
Ensure the previous route is stored and retrieved during redirects.
import { computed, ComputedRef, inject } from "vue"
import Auth from "./Auth"
import { authKey } from "./injectionKey"
import Options from "./type/Options"
import AuthDriver, { defineAuthDriver } from "./type/drivers/AuthDriver"
import HttpDriver, { defineHttpDriver } from "./type/drivers/HttpDriver"
import OAuth2Driver, { defineOAuth2Driver } from "./type/drivers/OAuth2Driver"
import { storePreviousRoute } from "./utils/index"
export function createAuth(options: Options) {
const auth = new Auth(options);
router.beforeEach((to, from, next) => {
if (to.meta.auth) {
storePreviousRoute(from.fullPath);
}
next();
});
return auth;
}
export function useAuth(key: symbol | string = authKey): Auth {
return inject(key) as Auth;
}
export function useUser<User extends object>(
key: symbol | string = authKey
): ComputedRef<User | null> {
const auth = useAuth(key);
return computed(() => {
return auth.user();
});
}
export type {
Options,
Auth,
AuthDriver,
HttpDriver,
OAuth2Driver,
defineAuthDriver,
defineHttpDriver,
defineOAuth2Driver,
}
How to replicate the bug
- Open a new browser tab.
- Navigate directly to a URL that requires authentication (e.g.,
https://domain/requires-auth). - Observe that the previous route information is not available, resulting in an empty
fromobject and an emptyauth.redirect()object.
By implementing the proposed changes, the previous route information will be correctly stored and retrieved, even when directly accessing a protected page. This should resolve the issue of the empty from object and auth.redirect() object.
Click here to create a Pull Request with the proposed solution
Files used for this task:
Changes on src/shims-router.d.ts
Analysis Report for src/shims-router.d.ts
Overview
The file src/shims-router.d.ts extends the vue-router module by adding custom properties to the RouteMeta interface. This interface is used to define metadata for routes in a Vue.js application.
Current Implementation
The RouteMeta interface currently includes the following properties:
auth: This can be a boolean, string, array ofRoles, or an object with additional properties such asroles,redirect,notFoundRedirect,forbiddenRedirect, androlesKey.
Issue Analysis
The issue described by the user is that when directly accessing a protected page (one that requires authentication), the previous route information is not available. This is problematic because the application needs to know the previous route to handle redirections properly.
Proposed Solution
To address this issue, we need to ensure that the RouteMeta interface includes properties for storing and retrieving the previous route information. This will allow the application to persist the previous route even when a user directly accesses a protected page.
Changes Required
- Add a Property for Previous Route Information: Extend the
RouteMetainterface to include a property for storing the previous route. This can be a simple string or a more complex object depending on the requirements.
Updated src/shims-router.d.ts
import "vue-router"
import Roles from "./type/Roles"
declare module "vue-router" {
// eslint-disable-next-line functional/prefer-type-literal
interface RouteMeta {
auth?:
| boolean
| string
| Roles[]
| {
roles?: Roles
redirect?:
| RouteLocationRaw
| ((to: RouteLocationNormalized) => RouteLocationRaw)
notFoundRedirect?:
| RouteLocationRaw
| ((to: RouteLocationNormalized) => RouteLocationRaw)
forbiddenRedirect?:
| RouteLocationRaw
| ((to: RouteLocationNormalized) => RouteLocationRaw)
rolesKey?: string
}
previousRoute?: string // Add this line to store the previous route information
}
}
Conclusion
By adding the previousRoute property to the RouteMeta interface, we can store and retrieve the previous route information. This will help in handling redirections properly when a user directly accesses a protected page. The next steps involve updating the authentication logic and integrating these changes into the application.
Changes on src/utils/index.ts
Analysis Report for src/utils/index.ts
Overview
The file src/utils/index.ts contains utility functions that are used throughout the application. These functions include type checking, array conversion, object comparison, and storage capability checks. The current issue requires adding utility functions for storing and retrieving the previous route information from local storage or a Vuex store.
Existing Functions
- isObject(val: any): boolean: Checks if a value is an object.
- toArray<T>(val: T): T[]: Converts a value to an array.
- compare<O, T>(one: O, two: T): boolean: Compares two objects or arrays.
- isLocalStorage(): boolean: Checks if local storage is available.
- isSessionStorage(): boolean: Checks if session storage is available.
- isCookieStorage(): boolean: Always returns true (placeholder for cookie storage check).
- getProperty(obj: any, desc: string): any: Retrieves a nested property from an object using a dot-separated string.
Required Additions
To address the issue, we need to add utility functions for:
- Storing the previous route information in local storage.
- Retrieving the previous route information from local storage.
Proposed Utility Functions
1. Storing Previous Route
export function storePreviousRoute(route: string): void {
if (isLocalStorage()) {
localStorage.setItem('previousRoute', route);
}
}
2. Retrieving Previous Route
export function getPreviousRoute(): string | null {
if (isLocalStorage()) {
return localStorage.getItem('previousRoute');
}
return null;
}
Integration Plan
- Update
src/Auth.ts: Modify the authentication logic to callstorePreviousRoutewhen redirecting to the login page. - Update
src/index.ts: Ensure the previous route is retrieved usinggetPreviousRouteduring redirects. - Update
src/shims-router.d.ts: Ensure theRouteMetainterface includes properties for storing and retrieving the previous route information.
Conclusion
The file src/utils/index.ts is relevant to the issue as it will house the utility functions needed for storing and retrieving the previous route information. The proposed additions should help in resolving the issue where the previous route is not available when directly accessing a protected page.
Changes on src/Auth.ts
Bug Analysis Report for src/Auth.ts
Issue Description
The user reported that when directly accessing a protected page (e.g., by opening a new browser tab and navigating to a URL that requires authentication), the application does not correctly store or retrieve the previous route information. This results in an empty from object and an empty auth.redirect() object.
Relevant Code Analysis
The src/Auth.ts file contains the authentication logic, including route guards and redirection handling. The key function related to the issue is the beforeEach navigation guard, which is responsible for handling route transitions and storing route information.
Key Observations
-
Route Guard Setup:
this.options.plugins?.router?.beforeEach(async (to, from, next) => { this.tPrev = this.tCurrent; this.tCurrent = from; await syncStorage(this); const authMeta = getAuthMeta(to); processTransitionEach(this, to, authMeta, (redirect) => { if (!redirect) { next(); return; } next(redirect); }); });- The
beforeEachhook updatestPrevandtCurrentwith the current and previous routes, respectively. - The
syncStoragefunction is called to ensure the authentication state is synchronized. - The
processTransitionEachfunction handles the redirection logic based on the route's metadata.
- The
-
Redirection Handling:
function processTransitionEach( auth: Auth, to: RouteLocationNormalized, authMeta: RouteMeta["auth"] | null, cb: (to?: RouteLocationRaw) => void ) { // ... existing logic ... if (!auth.check()) { auth.tStatusType = 401; if (typeof authRedirect === "function") { authRedirect = authRedirect(to); } cb(authRedirect); return; } // ... existing logic ... auth._redirect.value = auth.tStatusType ? { type: auth.tStatusType, from: auth.tPrev, to: auth.tCurrent, } : null; auth.tStatusType = null; cb(); }- The
processTransitionEachfunction sets the_redirectvalue based on the authentication status and route information. - The
auth.tPrevandauth.tCurrentvalues are used to populate thefromandtoproperties of the_redirectobject.
- The
Potential Cause of the Bug
- When directly accessing a protected page, the
fromroute is not set correctly because there is no previous route in a new browser tab context. This results inauth.tPrevbeingnull. - The current implementation does not handle the scenario where the
fromroute isnull, leading to an emptyfromobject in theauth.redirect()result.
Suggested Fix
To address this issue, we need to persist the previous route information in a more reliable manner, such as using local storage. This way, even when directly accessing a protected page, we can retrieve the previous route from local storage.
Proposed Code Changes
-
Update
beforeEachHook:this.options.plugins?.router?.beforeEach(async (to, from, next) => { if (from.name) { this.tPrev = from; localStorage.setItem('previousRoute', JSON.stringify(from)); } else { const storedPrevRoute = localStorage.getItem('previousRoute'); this.tPrev = storedPrevRoute ? JSON.parse(storedPrevRoute) : null; } this.tCurrent = to; await syncStorage(this); const authMeta = getAuthMeta(to); processTransitionEach(this, to, authMeta, (redirect) => { if (!redirect) { next(); return; } next(redirect); }); }); -
Clear Previous Route on Logout:
function logout(auth: Auth, redirect?: RouteLocationRaw) { $cookie.remove(auth, auth.options.tokenImpersonateKey); $cookie.remove(auth, auth.options.tokenDefaultKey); $token.remove(auth, auth.options.tokenImpersonateKey); $token.remove(auth, auth.options.tokenDefaultKey); $token.remove(auth, auth.options.staySignedInKey); $token.remove(auth, auth.options.userKey); auth.state.loaded = true; auth.state.authenticated = false; auth.state.data = null; localStorage.removeItem('previousRoute'); routerPush(auth, redirect); }
Conclusion
The proposed changes should ensure that the previous route information is correctly stored and retrieved, even when directly accessing a protected page. This should resolve the issue of the empty from object and auth.redirect() object.
Changes on src/index.ts
Analysis of src/index.ts
The src/index.ts file is primarily responsible for setting up and providing authentication-related functionalities. It includes functions to create and use an Auth instance, as well as to retrieve the current user. Here is a breakdown of the key components:
-
Imports:
- Vue's
computed,ComputedRef, andinjectfunctions. Authclass andauthKeyfrom local modules.- Types and drivers related to authentication (
Options,AuthDriver,HttpDriver,OAuth2Driver).
- Vue's
-
Functions:
createAuth(options: Options): Initializes and returns a newAuthinstance.useAuth(key: symbol | string = authKey): Injects and returns theAuthinstance using the provided key.useUser<User extends object>(key: symbol | string = authKey): ComputedRef<User | null>: Returns a computed reference to the current user.
-
Exports:
- Functions:
createAuth,useAuth,useUser. - Types:
Options,Auth,AuthDriver,HttpDriver,OAuth2Driver,defineAuthDriver,defineHttpDriver,defineOAuth2Driver.
- Functions:
Potential Cause of the Bug
The issue described involves the inability to retrieve the previous route when directly accessing a protected page. The src/index.ts file does not currently handle the storage or retrieval of the previous route. This functionality needs to be integrated into the authentication logic.
Suggested Changes
To address the issue, the following changes should be made:
-
Update
AuthClass:- Modify the
Authclass to store the previous route in a persistent manner (e.g., local storage) when a user directly accesses a protected page.
- Modify the
-
Integrate Updated Logic:
- Ensure that the
createAuthanduseAuthfunctions insrc/index.tsutilize the updatedAuthclass to handle the previous route information.
- Ensure that the
-
Utility Functions:
- Add utility functions for storing and retrieving the previous route information from local storage or a Vuex store. This can be done in the
src/utils/index.tsfile.
- Add utility functions for storing and retrieving the previous route information from local storage or a Vuex store. This can be done in the
Example Implementation
Here is an example of how you might modify the Auth class and integrate it into src/index.ts:
Update Auth Class (in src/Auth.ts)
class Auth {
// Existing properties and methods...
// New method to store the previous route
storePreviousRoute(route: string) {
localStorage.setItem('previousRoute', route);
}
// New method to retrieve the previous route
getPreviousRoute(): string | null {
return localStorage.getItem('previousRoute');
}
}
Integrate in src/index.ts
import { computed, ComputedRef, inject } from "vue"
import Auth from "./Auth"
import { authKey } from "./injectionKey"
import Options from "./type/Options"
import AuthDriver, { defineAuthDriver } from "./type/drivers/AuthDriver"
import HttpDriver, { defineHttpDriver } from "./type/drivers/HttpDriver"
import OAuth2Driver, { defineOAuth2Driver } from "./type/drivers/OAuth2Driver"
export function createAuth(options: Options) {
const auth = new Auth(options);
// Example of storing the previous route
router.beforeEach((to, from, next) => {
if (to.meta.auth) {
auth.storePreviousRoute(from.fullPath);
}
next();
});
return auth;
}
export function useAuth(key: symbol | string = authKey): Auth {
return inject(key) as Auth;
}
export function useUser<User extends object>(
key: symbol | string = authKey
): ComputedRef<User | null> {
const auth = useAuth(key);
return computed(() => {
return auth.user();
});
}
export type {
Options,
Auth,
AuthDriver,
HttpDriver,
OAuth2Driver,
defineAuthDriver,
defineHttpDriver,
defineOAuth2Driver,
}
Conclusion
The src/index.ts file itself does not directly cause the bug but needs to integrate with the updated Auth class to handle the previous route information. The primary changes should be made in the Auth class and then utilized in src/index.ts to ensure the previous route is correctly stored and retrieved during redirects.
.redirect() will only return the page doing the redirect when it comes from vue-router can you make me a minimal reproducible example?
Hey @tachibana-shin, Thanks for getting in touch with me.
See here: https://github.com/nimashoghi0/vue-auth
When you hit /demo directly, vue-auth-3 redirects to the /login page, and there is no data on the previous page (/demo), vue router also doesn't show any info so that's probably why.
I need to capture this previous data, so I will write my own wrapper to check authorisation, where I can store the information in local storage, it would have been great to use your meta field checker though. Maybe something to consider in the future?
Cheers