nextauthz 1.3.33 → 1.3.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import React, { ReactNode } from 'react';
2
+ import { ReactNode } from 'react';
3
3
 
4
4
  type User = {
5
5
  [key: string]: any;
@@ -24,22 +24,19 @@ declare function createAuthContext<UserType extends User = User>(option?: {
24
24
  children: ReactNode;
25
25
  }) => react_jsx_runtime.JSX.Element;
26
26
  useAuth: () => AuthContextType<UserType>;
27
- tokenKey: string;
28
27
  };
29
28
 
30
- type AuthGuardProps = {
29
+ declare const AuthGuard: ({ children, redirectTo, fallback, }: {
31
30
  children: React.ReactNode;
32
31
  redirectTo?: string;
33
32
  fallback?: React.ReactNode;
34
- };
35
- declare const AuthGuard: ({ children, redirectTo, fallback, }: AuthGuardProps) => react_jsx_runtime.JSX.Element | null;
33
+ }) => react_jsx_runtime.JSX.Element | null;
36
34
 
37
- type RoleGuardProps = {
35
+ declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: {
38
36
  children: React.ReactNode;
39
37
  allowedRoles: string[];
40
38
  redirectTo?: string;
41
39
  fallback?: React.ReactNode;
42
- };
43
- declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
40
+ }) => react_jsx_runtime.JSX.Element | null;
44
41
 
45
42
  export { AuthGuard, RoleGuard, createAuthContext };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import React, { ReactNode } from 'react';
2
+ import { ReactNode } from 'react';
3
3
 
4
4
  type User = {
5
5
  [key: string]: any;
@@ -24,22 +24,19 @@ declare function createAuthContext<UserType extends User = User>(option?: {
24
24
  children: ReactNode;
25
25
  }) => react_jsx_runtime.JSX.Element;
26
26
  useAuth: () => AuthContextType<UserType>;
27
- tokenKey: string;
28
27
  };
29
28
 
30
- type AuthGuardProps = {
29
+ declare const AuthGuard: ({ children, redirectTo, fallback, }: {
31
30
  children: React.ReactNode;
32
31
  redirectTo?: string;
33
32
  fallback?: React.ReactNode;
34
- };
35
- declare const AuthGuard: ({ children, redirectTo, fallback, }: AuthGuardProps) => react_jsx_runtime.JSX.Element | null;
33
+ }) => react_jsx_runtime.JSX.Element | null;
36
34
 
37
- type RoleGuardProps = {
35
+ declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: {
38
36
  children: React.ReactNode;
39
37
  allowedRoles: string[];
40
38
  redirectTo?: string;
41
39
  fallback?: React.ReactNode;
42
- };
43
- declare const RoleGuard: ({ children, allowedRoles, redirectTo, fallback, }: RoleGuardProps) => react_jsx_runtime.JSX.Element | null;
40
+ }) => react_jsx_runtime.JSX.Element | null;
44
41
 
45
42
  export { AuthGuard, RoleGuard, createAuthContext };
package/dist/index.js CHANGED
@@ -65,90 +65,82 @@ function createAuthContext(option) {
65
65
  const storage = option?.storage ?? "cookie";
66
66
  const tokenKey = option?.tokenKey ?? "access_token";
67
67
  const rolePath = option?.rolePath ?? "role";
68
+ const manager = (0, import_react_token_manager.useTokenManager)();
69
+ const user = useAuthStore((s) => s.user);
70
+ const role = useAuthStore((s) => s.role);
71
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
72
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
73
+ const setUser = useAuthStore((s) => s.setUser);
74
+ const setRole = useAuthStore((s) => s.setRole);
75
+ const setAuth = useAuthStore((s) => s.setAuth);
76
+ const setAuthChecked = useAuthStore((s) => s.setAuthChecked);
77
+ const resetAuth = useAuthStore((s) => s.resetAuth);
68
78
  (0, import_react.useEffect)(() => {
69
79
  (0, import_react_token_manager.configureTokenManager)({ storage });
70
80
  }, [storage]);
71
- const manager = (0, import_react_token_manager.useTokenManager)();
72
- const {
73
- user,
74
- role,
75
- setUser,
76
- setRole,
77
- resetAuth,
78
- isAuthChecked,
79
- isAuthenticated,
80
- setAuth,
81
- setAuthChecked
82
- } = useAuthStore();
83
81
  const extractRole = (userObj) => {
84
- if (!userObj || !rolePath) return null;
82
+ if (!userObj) return null;
85
83
  return rolePath.split(".").reduce((acc, key) => acc?.[key], userObj) ?? null;
86
84
  };
87
85
  (0, import_react.useEffect)(() => {
88
86
  const storedUser = manager.getSingleToken("user");
89
87
  const token = manager.getSingleToken(tokenKey);
90
88
  if (token && !manager.isExpired(token)) {
91
- try {
92
- setAuth(true);
93
- if (storedUser) {
94
- const parsedUser = JSON.parse(storedUser);
95
- console.log("rolePath", rolePath);
96
- console.log("parsedUser", parsedUser);
97
- console.log("extractRole", extractRole(parsedUser));
98
- setUser(parsedUser);
99
- setRole(extractRole(parsedUser));
89
+ setAuth(true);
90
+ if (storedUser) {
91
+ try {
92
+ const parsed = JSON.parse(storedUser);
93
+ setUser(parsed);
94
+ setRole(extractRole(parsed));
95
+ } catch {
96
+ resetAuth();
100
97
  }
101
- } catch {
102
- resetAuth();
103
98
  }
104
99
  } else {
105
100
  resetAuth();
106
101
  }
107
102
  setAuthChecked(true);
108
103
  }, [tokenKey]);
109
- const login = (tokens, userData, role2) => {
110
- const tokenValue = tokens[tokenKey] ?? tokens["access_token"] ?? Object.values(tokens)[0];
111
- manager.setTokens({
112
- ...tokens,
113
- [tokenKey]: tokenValue,
114
- user: JSON.stringify(userData ?? null)
115
- });
116
- if (userData) setUser(userData);
117
- setRole(role2 ?? extractRole(userData));
118
- setAuth(true);
119
- setAuthChecked(true);
120
- };
121
- const logout = () => {
104
+ const login = (0, import_react.useCallback)(
105
+ (tokens, userData, role2) => {
106
+ const tokenValue = tokens[tokenKey] ?? tokens["access_token"] ?? Object.values(tokens)[0];
107
+ manager.setTokens({
108
+ ...tokens,
109
+ [tokenKey]: tokenValue,
110
+ user: JSON.stringify(userData ?? null)
111
+ });
112
+ if (userData) setUser(userData);
113
+ setRole(role2 ?? extractRole(userData));
114
+ setAuth(true);
115
+ setAuthChecked(true);
116
+ },
117
+ [tokenKey]
118
+ );
119
+ const logout = (0, import_react.useCallback)(() => {
122
120
  manager.clearTokens();
123
121
  resetAuth();
124
- };
125
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
126
- AuthContext.Provider,
127
- {
128
- value: {
129
- user,
130
- role,
131
- isAuthenticated,
132
- isAuthChecked,
133
- login,
134
- logout,
135
- setUser: (u) => setUser(u),
136
- tokenKey
137
- },
138
- children
139
- }
122
+ }, []);
123
+ const value = (0, import_react.useMemo)(
124
+ () => ({
125
+ user,
126
+ role,
127
+ isAuthenticated,
128
+ isAuthChecked,
129
+ login,
130
+ logout,
131
+ setUser,
132
+ tokenKey
133
+ }),
134
+ [user, role, isAuthenticated, isAuthChecked, login, logout, tokenKey]
140
135
  );
136
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value, children });
141
137
  };
142
138
  const useAuth = () => {
143
139
  const context = (0, import_react.useContext)(AuthContext);
144
140
  if (!context) throw new Error("useAuth must be used within AuthProvider");
145
141
  return context;
146
142
  };
147
- return {
148
- AuthProvider,
149
- useAuth,
150
- tokenKey: option?.tokenKey ?? "access_token"
151
- };
143
+ return { AuthProvider, useAuth };
152
144
  }
153
145
 
154
146
  // src/AuthGuard.tsx
@@ -161,18 +153,16 @@ var AuthGuard = ({
161
153
  fallback = null
162
154
  }) => {
163
155
  const router = (0, import_navigation.useRouter)();
164
- const { isAuthenticated, isAuthChecked } = useAuthStore();
156
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
157
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
165
158
  (0, import_react2.useEffect)(() => {
166
159
  if (!isAuthChecked) return;
167
160
  if (!isAuthenticated) {
168
161
  router.replace(redirectTo);
169
162
  }
170
- }, [isAuthenticated, isAuthChecked, redirectTo, router]);
163
+ }, [isAuthenticated, isAuthChecked]);
171
164
  if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback });
172
- if (!isAuthenticated) {
173
- router.replace(redirectTo);
174
- return null;
175
- }
165
+ if (!isAuthenticated) return null;
176
166
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
177
167
  };
178
168
  var AuthGuard_default = AuthGuard;
@@ -188,18 +178,17 @@ var RoleGuard = ({
188
178
  fallback = null
189
179
  }) => {
190
180
  const router = (0, import_navigation2.useRouter)();
191
- const { role, isAuthChecked, isAuthenticated } = useAuthStore();
181
+ const role = useAuthStore((s) => s.role);
182
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
183
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
192
184
  (0, import_react3.useEffect)(() => {
193
- if (!isAuthChecked) return;
185
+ if (!isAuthChecked || !isAuthenticated) return;
194
186
  if (!role || !allowedRoles.includes(role)) {
195
187
  router.replace(redirectTo);
196
188
  }
197
- }, [role, isAuthChecked, allowedRoles, redirectTo, router]);
189
+ }, [role, isAuthenticated, isAuthChecked]);
198
190
  if (!isAuthChecked) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: fallback });
199
- if (!isAuthenticated) {
200
- router.replace(redirectTo);
201
- return null;
202
- }
191
+ if (!isAuthenticated) return null;
203
192
  if (!role || !allowedRoles.includes(role)) return null;
204
193
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children });
205
194
  };
package/dist/index.mjs CHANGED
@@ -1,7 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  // src/AuthProvider.tsx
4
- import { createContext, useContext, useEffect } from "react";
4
+ import {
5
+ createContext,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useCallback
10
+ } from "react";
5
11
  import { configureTokenManager, useTokenManager } from "react-token-manager";
6
12
 
7
13
  // store/useGuardStore.ts
@@ -38,90 +44,82 @@ function createAuthContext(option) {
38
44
  const storage = option?.storage ?? "cookie";
39
45
  const tokenKey = option?.tokenKey ?? "access_token";
40
46
  const rolePath = option?.rolePath ?? "role";
47
+ const manager = useTokenManager();
48
+ const user = useAuthStore((s) => s.user);
49
+ const role = useAuthStore((s) => s.role);
50
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
51
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
52
+ const setUser = useAuthStore((s) => s.setUser);
53
+ const setRole = useAuthStore((s) => s.setRole);
54
+ const setAuth = useAuthStore((s) => s.setAuth);
55
+ const setAuthChecked = useAuthStore((s) => s.setAuthChecked);
56
+ const resetAuth = useAuthStore((s) => s.resetAuth);
41
57
  useEffect(() => {
42
58
  configureTokenManager({ storage });
43
59
  }, [storage]);
44
- const manager = useTokenManager();
45
- const {
46
- user,
47
- role,
48
- setUser,
49
- setRole,
50
- resetAuth,
51
- isAuthChecked,
52
- isAuthenticated,
53
- setAuth,
54
- setAuthChecked
55
- } = useAuthStore();
56
60
  const extractRole = (userObj) => {
57
- if (!userObj || !rolePath) return null;
61
+ if (!userObj) return null;
58
62
  return rolePath.split(".").reduce((acc, key) => acc?.[key], userObj) ?? null;
59
63
  };
60
64
  useEffect(() => {
61
65
  const storedUser = manager.getSingleToken("user");
62
66
  const token = manager.getSingleToken(tokenKey);
63
67
  if (token && !manager.isExpired(token)) {
64
- try {
65
- setAuth(true);
66
- if (storedUser) {
67
- const parsedUser = JSON.parse(storedUser);
68
- console.log("rolePath", rolePath);
69
- console.log("parsedUser", parsedUser);
70
- console.log("extractRole", extractRole(parsedUser));
71
- setUser(parsedUser);
72
- setRole(extractRole(parsedUser));
68
+ setAuth(true);
69
+ if (storedUser) {
70
+ try {
71
+ const parsed = JSON.parse(storedUser);
72
+ setUser(parsed);
73
+ setRole(extractRole(parsed));
74
+ } catch {
75
+ resetAuth();
73
76
  }
74
- } catch {
75
- resetAuth();
76
77
  }
77
78
  } else {
78
79
  resetAuth();
79
80
  }
80
81
  setAuthChecked(true);
81
82
  }, [tokenKey]);
82
- const login = (tokens, userData, role2) => {
83
- const tokenValue = tokens[tokenKey] ?? tokens["access_token"] ?? Object.values(tokens)[0];
84
- manager.setTokens({
85
- ...tokens,
86
- [tokenKey]: tokenValue,
87
- user: JSON.stringify(userData ?? null)
88
- });
89
- if (userData) setUser(userData);
90
- setRole(role2 ?? extractRole(userData));
91
- setAuth(true);
92
- setAuthChecked(true);
93
- };
94
- const logout = () => {
83
+ const login = useCallback(
84
+ (tokens, userData, role2) => {
85
+ const tokenValue = tokens[tokenKey] ?? tokens["access_token"] ?? Object.values(tokens)[0];
86
+ manager.setTokens({
87
+ ...tokens,
88
+ [tokenKey]: tokenValue,
89
+ user: JSON.stringify(userData ?? null)
90
+ });
91
+ if (userData) setUser(userData);
92
+ setRole(role2 ?? extractRole(userData));
93
+ setAuth(true);
94
+ setAuthChecked(true);
95
+ },
96
+ [tokenKey]
97
+ );
98
+ const logout = useCallback(() => {
95
99
  manager.clearTokens();
96
100
  resetAuth();
97
- };
98
- return /* @__PURE__ */ jsx(
99
- AuthContext.Provider,
100
- {
101
- value: {
102
- user,
103
- role,
104
- isAuthenticated,
105
- isAuthChecked,
106
- login,
107
- logout,
108
- setUser: (u) => setUser(u),
109
- tokenKey
110
- },
111
- children
112
- }
101
+ }, []);
102
+ const value = useMemo(
103
+ () => ({
104
+ user,
105
+ role,
106
+ isAuthenticated,
107
+ isAuthChecked,
108
+ login,
109
+ logout,
110
+ setUser,
111
+ tokenKey
112
+ }),
113
+ [user, role, isAuthenticated, isAuthChecked, login, logout, tokenKey]
113
114
  );
115
+ return /* @__PURE__ */ jsx(AuthContext.Provider, { value, children });
114
116
  };
115
117
  const useAuth = () => {
116
118
  const context = useContext(AuthContext);
117
119
  if (!context) throw new Error("useAuth must be used within AuthProvider");
118
120
  return context;
119
121
  };
120
- return {
121
- AuthProvider,
122
- useAuth,
123
- tokenKey: option?.tokenKey ?? "access_token"
124
- };
122
+ return { AuthProvider, useAuth };
125
123
  }
126
124
 
127
125
  // src/AuthGuard.tsx
@@ -134,18 +132,16 @@ var AuthGuard = ({
134
132
  fallback = null
135
133
  }) => {
136
134
  const router = useRouter();
137
- const { isAuthenticated, isAuthChecked } = useAuthStore();
135
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
136
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
138
137
  useEffect2(() => {
139
138
  if (!isAuthChecked) return;
140
139
  if (!isAuthenticated) {
141
140
  router.replace(redirectTo);
142
141
  }
143
- }, [isAuthenticated, isAuthChecked, redirectTo, router]);
142
+ }, [isAuthenticated, isAuthChecked]);
144
143
  if (!isAuthChecked) return /* @__PURE__ */ jsx2(Fragment, { children: fallback });
145
- if (!isAuthenticated) {
146
- router.replace(redirectTo);
147
- return null;
148
- }
144
+ if (!isAuthenticated) return null;
149
145
  return /* @__PURE__ */ jsx2(Fragment, { children });
150
146
  };
151
147
  var AuthGuard_default = AuthGuard;
@@ -161,18 +157,17 @@ var RoleGuard = ({
161
157
  fallback = null
162
158
  }) => {
163
159
  const router = useRouter2();
164
- const { role, isAuthChecked, isAuthenticated } = useAuthStore();
160
+ const role = useAuthStore((s) => s.role);
161
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
162
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked);
165
163
  useEffect3(() => {
166
- if (!isAuthChecked) return;
164
+ if (!isAuthChecked || !isAuthenticated) return;
167
165
  if (!role || !allowedRoles.includes(role)) {
168
166
  router.replace(redirectTo);
169
167
  }
170
- }, [role, isAuthChecked, allowedRoles, redirectTo, router]);
168
+ }, [role, isAuthenticated, isAuthChecked]);
171
169
  if (!isAuthChecked) return /* @__PURE__ */ jsx3(Fragment2, { children: fallback });
172
- if (!isAuthenticated) {
173
- router.replace(redirectTo);
174
- return null;
175
- }
170
+ if (!isAuthenticated) return null;
176
171
  if (!role || !allowedRoles.includes(role)) return null;
177
172
  return /* @__PURE__ */ jsx3(Fragment2, { children });
178
173
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextauthz",
3
- "version": "1.3.33",
3
+ "version": "1.3.36",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
package/readme.md CHANGED
@@ -2,111 +2,158 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- This authentication system provides:
5
+ ** NextAuthZ provides:
6
6
 
7
- ** Global auth state
7
+ ** Global auth state
8
8
 
9
- ** Token persistence
9
+ ** Token persistence
10
10
 
11
- ** Typed AuthProvider & useAuth
11
+ ** Typed AuthProvider & useAuth
12
12
 
13
- ** Route protection
13
+ ** Route protection (AuthGuard)
14
14
 
15
- ** Role-based access
15
+ ** Role-based protection (RoleGuard)
16
16
 
17
- ** Configurable storage system
17
+ ** Configurable storage system
18
+
19
+ ** Configurable role extraction (rolePath)
20
+
21
+ Works perfectly with Next.js App Router.
18
22
 
19
23
  ## Installation
20
24
 
21
25
  npm install nextauthz
22
26
 
27
+ ## Setup
23
28
 
24
- ## AuthProvider & useAuth
29
+ 1️⃣ Create Your Auth Instance
25
30
 
26
- Purpose
27
-
28
- Wrap your application with AuthProvider to provide global auth state (user, loading, error) and helper functions (login, logout, setUser).
31
+ Because NextAuthZ uses a factory pattern, you must create your own auth instance.
29
32
 
30
33
  ```bash
31
- import { createAppAuth } from 'nextauthz'
34
+ // src/lib/auth.ts
35
+ import { createAuthContext } from 'nextauthz'
36
+
37
+ export const {
38
+ AuthProvider,
39
+ useAuth,
40
+ } = createAuthContext({
41
+ storage: 'cookie', // 'localStorage' | 'sessionStorage' | 'cookie'
42
+ tokenKey: 'access_token', // optional (default: 'access_token')
43
+ rolePath: 'account_type', // optional (default: 'role')
44
+ })
45
+ ```
46
+
47
+ ## Available Options
32
48
 
33
- // Choose storage type
34
- const { AuthProvider, useAuth } = createAppAuth('cookie', 'access_token')
49
+ | Option | Type | Default | Description |
50
+ | ---------- | ------------------------------------------------ | ---------------- | ------------------------------------- |
51
+ | `storage` | `'localStorage' \| 'sessionStorage' \| 'cookie'` | `'cookie'` | Where tokens are stored |
52
+ | `tokenKey` | `string` | `'access_token'` | Key used for primary token |
53
+ | `rolePath` | `string` | `'role'` | Path to extract role from user object |
35
54
 
36
55
 
37
- // Options: 'localStorage' | 'sessionStorage' | 'cookie'
38
- // Defaults to 'cookie' if no storage is passed
39
56
 
40
- function App({ children }: { children: React.ReactNode }) {
57
+
58
+ ## 2️⃣ Wrap Your App
59
+
60
+ Wrap your application with AuthProvider to provide global auth state (user, loading, error) and helper functions (login, logout, setUser).
61
+
62
+ ```bash
63
+ // app/layout.tsx
64
+
65
+ import { AuthProvider } from '@/lib/auth'
66
+
67
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
41
68
  return <AuthProvider>{children}</AuthProvider>
42
69
  }
43
-
44
- export default App
45
70
  ```
46
71
 
47
- ## Hook: useAuth
72
+ ## useAuth Hook
48
73
 
49
74
  ```bash
50
- import { useAuth } from 'nextauthz'
51
-
52
- const { user, role, login, logout, setUser, isAuthenticated, isAuthChecked } = useAuth()
75
+ import { useAuth } from '@/lib/auth'
76
+
77
+ const {
78
+ user,
79
+ role,
80
+ isAuthenticated,
81
+ isAuthChecked,
82
+ login,
83
+ logout,
84
+ setUser,
85
+ } = useAuth()
53
86
  ```
54
87
 
55
88
  Available Properties
56
89
 
57
- | Name | Type | Description |
58
- | ----------------- | ------------------------------------ | ------------------------------------------------------------- |
59
- | `user` | `User \| null` | Current authenticated user |
60
- | `role` | `string \| null` | User role |
61
- | `isAuthenticated` | `boolean` | True if the user is logged in |
62
- | `isAuthChecked` | `boolean` | True if auth state has finished restoring from storage/token |
63
- | `login` | `(tokens, userData?, role?) => void` | Logs in a user, saves tokens, and optionally sets user + role |
64
- | `logout` | `() => void` | Clears auth tokens and resets state |
65
- | `setUser` | `(user: User \| null) => void` | Update user state manually |
90
+ | Name | Type | Description |
91
+ | ----------------- | ------------------------------------ | ---------------------------------------- |
92
+ | `user` | `User \| null` | Current authenticated user |
93
+ | `role` | `string \| null` | Extracted user role |
94
+ | `isAuthenticated` | `boolean` | True if logged in |
95
+ | `isAuthChecked` | `boolean` | True when auth hydration is complete |
96
+ | `login` | `(tokens, userData?, role?) => void` | Save tokens + optionally set user + role |
97
+ | `logout` | `() => void` | Clears tokens and resets auth |
98
+ | `setUser` | `(user \| null) => void` | Manually update user |
66
99
 
67
-
68
- Example: Logging in
100
+ Example: Logging In
69
101
 
70
102
  ```bash
71
- const handleLogin = () => {
103
+ const { login } = useAuth()
104
+
105
+ const handleLogin = async () => {
72
106
  const tokens = {
73
107
  access_token: 'abc123',
74
108
  refresh_token: 'xyz789',
75
109
  }
76
110
 
77
111
  const user = {
78
- id: 1,
112
+ id: '1',
79
113
  name: 'John',
80
- role: 'admin',
114
+ account_type: 'admin',
81
115
  }
82
116
 
83
- login(tokens, user, user.role)
117
+ login(tokens, user)
84
118
  }
119
+ ```
85
120
 
86
- const handleLogout = () => {
87
- logout(); // clears token and user
88
- router.replace('/authentication/login');
89
- };
121
+ If rolePath: 'account_type' is configured, role is automatically extracted.
122
+
123
+ You can also override it manually:
124
+
125
+ ``` bash
126
+ login(tokens, user, 'admin')
127
+ ```
128
+ ## Logging Out
129
+
130
+ ```bash
131
+ const { logout } = useAuth()
132
+
133
+ const handleLogin = async () => {
134
+ logout()
135
+ router.replace('/authentication/login');
136
+ }
90
137
  ```
91
138
 
92
139
  ## AuthGuard
93
140
 
94
141
  Purpose
95
142
 
96
- Protect routes/pages to ensure only authenticated users can access them.
143
+ Protect pages so only authenticated users can access them.
97
144
 
98
145
  Props
99
146
 
100
- | Name | Type | Default | Description |
101
- | ------------ | ----------- | -------- | -------------------------------------- |
102
- | `children` | `ReactNode` | required | Components to render if authenticated |
103
- | `redirectTo` | `string` | `/login` | Page to redirect unauthenticated users |
104
- | `fallback` | `ReactNode` | `null` | Optional loading or fallback UI |
147
+ | Name | Type | Default | Description |
148
+ | ------------ | ----------- | -------- | ---------------------------------- |
149
+ | `children` | `ReactNode` | required | Content to render if authenticated |
150
+ | `redirectTo` | `string` | `/login` | Redirect if not authenticated |
151
+ | `fallback` | `ReactNode` | `null` | Optional loading UI |
105
152
 
106
153
  ## Usage Example
107
154
 
108
155
  ```bash
109
- import AuthGuard from 'nextauthz'
156
+ import { AuthGuard } from 'nextauthz'
110
157
 
111
158
  function DashboardPage() {
112
159
  return (
@@ -118,36 +165,34 @@ function DashboardPage() {
118
165
  ```
119
166
  Behavior:
120
167
 
121
- ** Redirects to /login if the user is not authenticated.
168
+ ** Waits for isAuthChecked
169
+ ** Redirects to redirectTo value if not authenticated
170
+ ** Renders children if authenticated
122
171
 
123
172
 
124
173
  ## RoleGuard
125
174
 
126
- Purpose
175
+ Restrict access based on role.
127
176
 
128
- RoleGuard restricts access to certain pages or components based on a user’s role. It assumes authentication is already handled (e.g., via AuthGuard or useAuth) and only checks whether the user’s role is allowed to access the content.
177
+ Props
129
178
 
130
- | Name | Type | Default | Description |
131
- | -------------- | ----------- | --------------- | ------------------------------------------------------------- |
132
- | `children` | `ReactNode` | required | Components to render if the user role is allowed |
133
- | `allowedRoles` | `string[]` | required | List of roles allowed to access this page |
134
- | `redirectTo` | `string` | `/unauthorized` | Redirect page for unauthorized roles |
135
- | `fallback` | `ReactNode` | `null` | Optional loading or fallback UI displayed while checking role |
179
+ | Name | Type | Default | Description |
180
+ | -------------- | ----------- | --------------- | ---------------------------- |
181
+ | `children` | `ReactNode` | required | Content to render |
182
+ | `allowedRoles` | `string[]` | required | Allowed roles |
183
+ | `redirectTo` | `string` | `/unauthorized` | Redirect if role not allowed |
184
+ | `fallback` | `ReactNode` | `null` | Optional loading UI |
136
185
 
137
186
 
138
187
  Usage Example
139
188
 
140
189
  ```bash
141
- import RoleGuard from 'nextauthz'
142
- import { useAuth } from 'nextauthz'
143
-
144
- function AdminPage() {
145
- const { user } = useAuth()
190
+ import { RoleGuard } from 'nextauthz'
146
191
 
192
+ export default function AdminPage() {
147
193
  return (
148
- <RoleGuard allowedRoles={['admin']} fallback={<p>Checking permissions...</p>}>
194
+ <RoleGuard allowedRoles={['admin']} fallback={<p>Checking role...</p>}>
149
195
  <h1>Admin Dashboard</h1>
150
- <p>Welcome, {user?.name}</p>
151
196
  </RoleGuard>
152
197
  )
153
198
  }
@@ -155,19 +200,52 @@ function AdminPage() {
155
200
 
156
201
  Behavior:
157
202
 
158
- ** RoleGuard will show the fallback UI while the authentication state is loading (isAuthChecked is false).
159
- **
203
+ ** Waits for isAuthChecked
204
+ ** Redirects if role not in allowedRoles
205
+ ** Blocks rendering if unauthorized
160
206
 
161
207
 
162
208
  ## Storage Options
163
209
 
164
210
  You can configure token storage:
165
211
 
166
- | Storage Type | Description |
167
- | ---------------- | ------------------------------- |
168
- | `localStorage` | Persists until manually cleared |
169
- | `sessionStorage` | Clears on tab close |
170
- | `cookie` | Cookie-based storage (default) |
212
+ | Storage Type | Behavior |
213
+ | ---------------- | ---------------------- |
214
+ | `localStorage` | Persists until cleared |
215
+ | `sessionStorage` | Clears on tab close |
216
+ | `cookie` | Cookie-based (default) |
217
+
218
+ ## Role Path Examples
219
+ Given this user:
220
+
221
+ ```bash
222
+ {
223
+ id: "1",
224
+ email: "user@email.com",
225
+ account_type: "artist"
226
+ }
227
+ ```
228
+
229
+ Use:
230
+
231
+ ```bash
232
+ rolePath: 'account_type'
233
+ ```
234
+
235
+ Nested example:
236
+ ```bash
237
+ {
238
+ profile: {
239
+ role: 'admin'
240
+ }
241
+ }
242
+ ```
243
+
244
+ Use:
245
+
246
+ ```bash
247
+ rolePath: 'profile.role'
248
+ ```
171
249
 
172
250
 
173
251
  ## Why NextAuthZ?
@@ -184,4 +262,8 @@ You can configure token storage:
184
262
 
185
263
  ** Lightweight
186
264
 
187
- ** No opinionated backend
265
+ ** No opinionated backend
266
+
267
+ ** Backend agnostic
268
+
269
+ ** Factory-based (multiple auth instances supported)
package/src/AuthGuard.tsx CHANGED
@@ -1,41 +1,34 @@
1
1
  'use client'
2
2
 
3
- import React, { useEffect } from 'react'
3
+ import { useEffect } from 'react'
4
4
  import { useRouter } from 'next/navigation'
5
5
  import { useAuthStore } from '../store/useGuardStore'
6
6
 
7
- type AuthGuardProps = {
8
- children: React.ReactNode
9
- redirectTo?: string
10
- fallback?: React.ReactNode
11
- }
12
-
13
7
  const AuthGuard = ({
14
8
  children,
15
9
  redirectTo = '/login',
16
10
  fallback = null,
17
- }: AuthGuardProps) => {
11
+ }: {
12
+ children: React.ReactNode
13
+ redirectTo?: string
14
+ fallback?: React.ReactNode
15
+ }) => {
18
16
  const router = useRouter()
19
17
 
20
- const { isAuthenticated, isAuthChecked } = useAuthStore()
18
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
19
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked)
21
20
 
22
21
  useEffect(() => {
23
22
  if (!isAuthChecked) return
24
-
25
23
  if (!isAuthenticated) {
26
24
  router.replace(redirectTo)
27
25
  }
28
- }, [isAuthenticated, isAuthChecked, redirectTo, router])
26
+ }, [isAuthenticated, isAuthChecked])
29
27
 
30
28
  if (!isAuthChecked) return <>{fallback}</>
31
-
32
- if (!isAuthenticated) {
33
- router.replace(redirectTo)
34
- return null
35
- }
36
-
29
+ if (!isAuthenticated) return null
37
30
 
38
31
  return <>{children}</>
39
32
  }
40
33
 
41
- export default AuthGuard
34
+ export default AuthGuard
@@ -1,13 +1,16 @@
1
1
  'use client'
2
2
 
3
- import { createContext, useContext, ReactNode, useEffect } from 'react'
3
+ import {
4
+ createContext,
5
+ useContext,
6
+ ReactNode,
7
+ useEffect,
8
+ useMemo,
9
+ useCallback,
10
+ } from 'react'
4
11
  import { configureTokenManager, useTokenManager } from 'react-token-manager'
5
12
  import { useAuthStore, User } from '../store/useGuardStore'
6
13
 
7
- /* ---------------------------------- */
8
- /* Types */
9
- /* ---------------------------------- */
10
-
11
14
  export type AuthContextType<UserType extends User = User> = {
12
15
  user: UserType | null
13
16
  role: string | null
@@ -19,14 +22,10 @@ export type AuthContextType<UserType extends User = User> = {
19
22
  tokenKey: string
20
23
  }
21
24
 
22
- /* ---------------------------------- */
23
- /* Factory */
24
- /* ---------------------------------- */
25
-
26
25
  export function createAuthContext<UserType extends User = User>(option?: {
27
26
  storage?: 'localStorage' | 'sessionStorage' | 'cookie'
28
27
  tokenKey?: string
29
- rolePath?: string // e.g. 'role' or 'profile.role'
28
+ rolePath?: string
30
29
  }) {
31
30
  const AuthContext = createContext<AuthContextType<UserType> | undefined>(undefined)
32
31
 
@@ -35,112 +34,94 @@ export function createAuthContext<UserType extends User = User>(option?: {
35
34
  const tokenKey = option?.tokenKey ?? 'access_token'
36
35
  const rolePath = option?.rolePath ?? 'role'
37
36
 
37
+ const manager = useTokenManager()
38
+
39
+ // ✅ Selectors (IMPORTANT)
40
+ const user = useAuthStore((s) => s.user)
41
+ const role = useAuthStore((s) => s.role)
42
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
43
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked)
44
+
45
+ const setUser = useAuthStore((s) => s.setUser)
46
+ const setRole = useAuthStore((s) => s.setRole)
47
+ const setAuth = useAuthStore((s) => s.setAuth)
48
+ const setAuthChecked = useAuthStore((s) => s.setAuthChecked)
49
+ const resetAuth = useAuthStore((s) => s.resetAuth)
50
+
38
51
  useEffect(() => {
39
52
  configureTokenManager({ storage })
40
53
  }, [storage])
41
54
 
42
- const manager = useTokenManager()
43
- const {
44
- user,
45
- role,
46
- setUser,
47
- setRole,
48
- resetAuth,
49
- isAuthChecked,
50
- isAuthenticated,
51
- setAuth,
52
- setAuthChecked,
53
- } = useAuthStore()
54
-
55
- /* ---------------------------------- */
56
- /* Helper: get role from path */
57
- /* ---------------------------------- */
58
55
  const extractRole = (userObj: any) => {
59
- if (!userObj || !rolePath) return null
56
+ if (!userObj) return null
60
57
  return rolePath.split('.').reduce((acc: any, key: string) => acc?.[key], userObj) ?? null
61
58
  }
62
-
63
-
64
- /* ---------------------------------- */
65
- /* Hydrate user from storage */
66
- /* ---------------------------------- */
67
59
 
60
+ // ✅ Hydrate once
68
61
  useEffect(() => {
69
62
  const storedUser = manager.getSingleToken('user')
70
63
  const token = manager.getSingleToken(tokenKey)
71
64
 
72
65
  if (token && !manager.isExpired(token)) {
73
- try {
74
- setAuth(true)
75
-
76
- if (storedUser) {
77
- const parsedUser = JSON.parse(storedUser) as UserType
78
- console.log('rolePath', rolePath);
79
- console.log('parsedUser', parsedUser);
80
- console.log('extractRole', extractRole(parsedUser));
81
- setUser(parsedUser)
82
- setRole(extractRole(parsedUser))
66
+ setAuth(true)
67
+
68
+ if (storedUser) {
69
+ try {
70
+ const parsed = JSON.parse(storedUser)
71
+ setUser(parsed)
72
+ setRole(extractRole(parsed))
73
+ } catch {
74
+ resetAuth()
83
75
  }
84
- } catch {
85
- resetAuth()
86
76
  }
87
77
  } else {
88
78
  resetAuth()
89
79
  }
90
80
 
91
81
  setAuthChecked(true)
92
- // eslint-disable-next-line react-hooks/exhaustive-deps
93
82
  }, [tokenKey])
94
83
 
95
- /* ---------------------------------- */
96
- /* Login */
97
- /* ---------------------------------- */
98
-
99
- const login = (tokens: Record<string, string>, userData?: UserType, role?: string) => {
100
- const tokenValue = tokens[tokenKey] ?? tokens['access_token'] ?? Object.values(tokens)[0]
101
-
102
- manager.setTokens({
103
- ...tokens,
104
- [tokenKey]: tokenValue,
105
- user: JSON.stringify(userData ?? null),
106
- })
107
-
108
- if (userData) setUser(userData)
109
- setRole(role ?? extractRole(userData))
110
- setAuth(true)
111
- setAuthChecked(true)
112
- }
113
-
114
- /* ---------------------------------- */
115
- /* Logout */
116
- /* ---------------------------------- */
84
+ const login = useCallback(
85
+ (tokens: Record<string, string>, userData?: UserType, role?: string) => {
86
+ const tokenValue =
87
+ tokens[tokenKey] ?? tokens['access_token'] ?? Object.values(tokens)[0]
88
+
89
+ manager.setTokens({
90
+ ...tokens,
91
+ [tokenKey]: tokenValue,
92
+ user: JSON.stringify(userData ?? null),
93
+ })
94
+
95
+ if (userData) setUser(userData)
96
+ setRole(role ?? extractRole(userData))
97
+ setAuth(true)
98
+ setAuthChecked(true)
99
+ },
100
+ [tokenKey]
101
+ )
117
102
 
118
- const logout = () => {
103
+ const logout = useCallback(() => {
119
104
  manager.clearTokens()
120
105
  resetAuth()
121
- }
122
-
123
- return (
124
- <AuthContext.Provider
125
- value={{
126
- user: user as UserType | null,
127
- role,
128
- isAuthenticated,
129
- isAuthChecked,
130
- login,
131
- logout,
132
- setUser: (u) => setUser(u),
133
- tokenKey,
134
- }}
135
- >
136
- {children}
137
- </AuthContext.Provider>
106
+ }, [])
107
+
108
+ // ✅ MEMOIZED CONTEXT VALUE
109
+ const value = useMemo(
110
+ () => ({
111
+ user: user as UserType | null,
112
+ role,
113
+ isAuthenticated,
114
+ isAuthChecked,
115
+ login,
116
+ logout,
117
+ setUser,
118
+ tokenKey,
119
+ }),
120
+ [user, role, isAuthenticated, isAuthChecked, login, logout, tokenKey]
138
121
  )
139
- }
140
122
 
141
- /* ---------------------------------- */
142
- /* Hook */
143
- /* ---------------------------------- */
123
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>
124
+ }
144
125
 
145
126
  const useAuth = () => {
146
127
  const context = useContext(AuthContext)
@@ -148,9 +129,5 @@ export function createAuthContext<UserType extends User = User>(option?: {
148
129
  return context
149
130
  }
150
131
 
151
- return {
152
- AuthProvider,
153
- useAuth,
154
- tokenKey: option?.tokenKey ?? 'access_token',
155
- }
156
- }
132
+ return { AuthProvider, useAuth }
133
+ }
package/src/RoleGuard.tsx CHANGED
@@ -1,45 +1,39 @@
1
1
  'use client'
2
2
 
3
- import React, { useEffect } from 'react'
3
+ import { useEffect } from 'react'
4
4
  import { useRouter } from 'next/navigation'
5
5
  import { useAuthStore } from '../store/useGuardStore'
6
6
 
7
- type RoleGuardProps = {
8
- children: React.ReactNode
9
- allowedRoles: string[]
10
- redirectTo?: string
11
- fallback?: React.ReactNode
12
- }
13
-
14
7
  const RoleGuard = ({
15
8
  children,
16
9
  allowedRoles,
17
10
  redirectTo = '/unauthorized',
18
11
  fallback = null,
19
- }: RoleGuardProps) => {
12
+ }: {
13
+ children: React.ReactNode
14
+ allowedRoles: string[]
15
+ redirectTo?: string
16
+ fallback?: React.ReactNode
17
+ }) => {
20
18
  const router = useRouter()
21
- const { role, isAuthChecked, isAuthenticated } = useAuthStore()
19
+
20
+ const role = useAuthStore((s) => s.role)
21
+ const isAuthenticated = useAuthStore((s) => s.isAuthenticated)
22
+ const isAuthChecked = useAuthStore((s) => s.isAuthChecked)
22
23
 
23
24
  useEffect(() => {
24
- if (!isAuthChecked) return
25
+ if (!isAuthChecked || !isAuthenticated) return
25
26
 
26
- // If role not allowed, redirect
27
27
  if (!role || !allowedRoles.includes(role)) {
28
28
  router.replace(redirectTo)
29
29
  }
30
- }, [role, isAuthChecked, allowedRoles, redirectTo, router])
30
+ }, [role, isAuthenticated, isAuthChecked])
31
31
 
32
32
  if (!isAuthChecked) return <>{fallback}</>
33
-
34
- if (!isAuthenticated) {
35
- router.replace(redirectTo)
36
- return null
37
- }
38
-
39
- // Block rendering if role is not allowed
33
+ if (!isAuthenticated) return null
40
34
  if (!role || !allowedRoles.includes(role)) return null
41
35
 
42
36
  return <>{children}</>
43
37
  }
44
38
 
45
- export default RoleGuard
39
+ export default RoleGuard