@zhmdff/auth-react 1.0.0

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/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @zhmdff/auth-react
2
+
3
+ Plug-and-play authentication provider for React and Next.js applications, designed to work seamlessly with the Zhmdff Auth .NET backend.
4
+
5
+ ## Features
6
+
7
+ - 🔐 **Pre-built Auth Context**: Manages access tokens, user state, and loading status.
8
+ - 🔄 **Auto-Refresh**: Automatically refreshes access tokens on 401 Unauthorized responses.
9
+ - 📡 **Smart Fetch Wrapper**: Includes a `fetch` utility that automatically injects the Bearer token.
10
+ - 🎣 **Simple Hook**: `useAuth()` hook for easy access to user data and actions.
11
+ - ⚛️ **React & Next.js Compatible**: Works in any React 18+ environment.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @zhmdff/auth-react
17
+ # or
18
+ yarn add @zhmdff/auth-react
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Wrap your application with `AuthProvider`
24
+
25
+ **Next.js (App Router) - `app/layout.tsx`**
26
+
27
+ ```tsx
28
+ import { AuthProvider } from "@zhmdff/auth-react";
29
+
30
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
31
+ return (
32
+ <html lang="en">
33
+ <body>
34
+ <AuthProvider
35
+ authUrl="http://localhost:5129/auth"
36
+ apiUrl="http://localhost:5129/api"
37
+ >
38
+ {children}
39
+ </AuthProvider>
40
+ </body>
41
+ </html>
42
+ );
43
+ }
44
+ ```
45
+
46
+ **React (Vite/CRA) - `src/main.tsx`**
47
+
48
+ ```tsx
49
+ import React from 'react';
50
+ import ReactDOM from 'react-dom/client';
51
+ import { AuthProvider } from "@zhmdff/auth-react";
52
+ import App from './App';
53
+
54
+ ReactDOM.createRoot(document.getElementById('root')!).render(
55
+ <React.StrictMode>
56
+ <AuthProvider
57
+ authUrl="http://localhost:5129/auth"
58
+ apiUrl="http://localhost:5129/api"
59
+ >
60
+ <App />
61
+ </AuthProvider>
62
+ </React.StrictMode>,
63
+ );
64
+ ```
65
+
66
+ ### 2. Use authentication in your components
67
+
68
+ ```tsx
69
+ import { useAuth } from "@zhmdff/auth-react";
70
+
71
+ export default function UserProfile() {
72
+ const { user, logout, isLoading } = useAuth();
73
+
74
+ if (isLoading) return <div>Loading...</div>;
75
+
76
+ if (!user) {
77
+ return <div>Please log in to view this page.</div>;
78
+ }
79
+
80
+ return (
81
+ <div>
82
+ <h1>Welcome, {user.fullName || user.username}!</h1>
83
+ <p>Role: {user.role}</p>
84
+ <button onClick={logout}>Sign Out</button>
85
+ </div>
86
+ );
87
+ }
88
+ ```
89
+
90
+ ## API Reference
91
+
92
+ ### `AuthProvider` Props
93
+
94
+ | Prop | Type | Required | Description |
95
+ | :--- | :--- | :--- | :--- |
96
+ | `authUrl` | `string` | Yes | The base URL for authentication endpoints (e.g., `http://localhost:5129/auth`). |
97
+ | `apiUrl` | `string` | No | The base URL for your API. Used by the `fetch` utility to resolve relative paths. |
98
+ | `children` | `ReactNode` | Yes | The components to be wrapped. |
99
+
100
+ ### `useAuth()` Hook
101
+
102
+ Returns an object with the following properties:
103
+
104
+ - `user`: `User | null` - The current authenticated user.
105
+ - `accessToken`: `string | null` - The current JWT access token.
106
+ - `isLoading`: `boolean` - True while the initial auth check is running.
107
+ - `checkAuth`: `() => Promise<boolean>` - Manually triggers an auth check (refresh token).
108
+ - `logout`: `() => Promise<void>` - Logs the user out and clears state.
109
+ - `fetch`: `(endpoint: string, options?: any) => Promise<any>` - Authenticated fetch wrapper.
110
+
111
+ ### Authenticated Fetch
112
+
113
+ The `fetch` function from `useAuth()` is a wrapper around the native `fetch` API. It:
114
+ 1. Automatically adds the `Authorization: Bearer <token>` header.
115
+ 2. Prepends your `apiUrl` to the request path.
116
+ 3. Intercepts 401 errors to attempt a silent token refresh.
117
+ 4. Retries the request if refresh succeeds, or logs the user out if it fails.
118
+
119
+ ```tsx
120
+ const { fetch } = useAuth();
121
+
122
+ const loadData = async () => {
123
+ try {
124
+ // Requests http://localhost:5129/api/dashboard-data
125
+ const data = await fetch("/dashboard-data");
126
+ console.log(data);
127
+ } catch (err) {
128
+ console.error("Failed to load data", err);
129
+ }
130
+ };
131
+ ```
@@ -0,0 +1,65 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ interface User {
5
+ id: number;
6
+ email?: string;
7
+ username?: string;
8
+ fullName?: string;
9
+ role: string;
10
+ adminLevel: number;
11
+ isActive: boolean;
12
+ createdAt: string;
13
+ lastLoginAt?: string;
14
+ }
15
+ interface AuthResult {
16
+ success: boolean;
17
+ accessToken?: string;
18
+ refreshToken?: string;
19
+ errorMessage?: string;
20
+ user?: User;
21
+ }
22
+ type AuthContextType = {
23
+ accessToken: string | null;
24
+ setAccessToken: (token: string | null) => void;
25
+ user: User | null;
26
+ setUser: (user: User | null) => void;
27
+ isLoading: boolean;
28
+ checkAuth: () => Promise<boolean>;
29
+ logout: () => Promise<void>;
30
+ fetch: (endpoint: string, options?: any) => Promise<any>;
31
+ };
32
+ interface LoginRequest {
33
+ identifier: string;
34
+ password: string;
35
+ }
36
+ interface RegisterRequest {
37
+ identifier: string;
38
+ password: string;
39
+ fullName?: string;
40
+ role?: string;
41
+ adminLevel?: number;
42
+ }
43
+
44
+ declare const AuthContext: React.Context<AuthContextType>;
45
+ interface AuthProviderProps {
46
+ children: React.ReactNode;
47
+ authUrl: string;
48
+ apiUrl?: string;
49
+ storageKey?: string;
50
+ }
51
+ declare const AuthProvider: ({ children, authUrl, apiUrl }: AuthProviderProps) => react_jsx_runtime.JSX.Element;
52
+ declare const useAuth: () => AuthContextType;
53
+
54
+ type FetchOptions = {
55
+ method?: "GET" | "POST" | "PUT" | "DELETE";
56
+ body?: any;
57
+ token?: string | null;
58
+ onTokenRefresh?: (token: string) => void;
59
+ onAuthFail?: () => void;
60
+ apiUrl?: string;
61
+ authUrl?: string;
62
+ };
63
+ declare function apiFetch(endpoint: string, options?: FetchOptions): Promise<any>;
64
+
65
+ export { AuthContext, type AuthContextType, AuthProvider, type AuthProviderProps, type AuthResult, type FetchOptions, type LoginRequest, type RegisterRequest, type User, apiFetch, useAuth };
@@ -0,0 +1,65 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+
4
+ interface User {
5
+ id: number;
6
+ email?: string;
7
+ username?: string;
8
+ fullName?: string;
9
+ role: string;
10
+ adminLevel: number;
11
+ isActive: boolean;
12
+ createdAt: string;
13
+ lastLoginAt?: string;
14
+ }
15
+ interface AuthResult {
16
+ success: boolean;
17
+ accessToken?: string;
18
+ refreshToken?: string;
19
+ errorMessage?: string;
20
+ user?: User;
21
+ }
22
+ type AuthContextType = {
23
+ accessToken: string | null;
24
+ setAccessToken: (token: string | null) => void;
25
+ user: User | null;
26
+ setUser: (user: User | null) => void;
27
+ isLoading: boolean;
28
+ checkAuth: () => Promise<boolean>;
29
+ logout: () => Promise<void>;
30
+ fetch: (endpoint: string, options?: any) => Promise<any>;
31
+ };
32
+ interface LoginRequest {
33
+ identifier: string;
34
+ password: string;
35
+ }
36
+ interface RegisterRequest {
37
+ identifier: string;
38
+ password: string;
39
+ fullName?: string;
40
+ role?: string;
41
+ adminLevel?: number;
42
+ }
43
+
44
+ declare const AuthContext: React.Context<AuthContextType>;
45
+ interface AuthProviderProps {
46
+ children: React.ReactNode;
47
+ authUrl: string;
48
+ apiUrl?: string;
49
+ storageKey?: string;
50
+ }
51
+ declare const AuthProvider: ({ children, authUrl, apiUrl }: AuthProviderProps) => react_jsx_runtime.JSX.Element;
52
+ declare const useAuth: () => AuthContextType;
53
+
54
+ type FetchOptions = {
55
+ method?: "GET" | "POST" | "PUT" | "DELETE";
56
+ body?: any;
57
+ token?: string | null;
58
+ onTokenRefresh?: (token: string) => void;
59
+ onAuthFail?: () => void;
60
+ apiUrl?: string;
61
+ authUrl?: string;
62
+ };
63
+ declare function apiFetch(endpoint: string, options?: FetchOptions): Promise<any>;
64
+
65
+ export { AuthContext, type AuthContextType, AuthProvider, type AuthProviderProps, type AuthResult, type FetchOptions, type LoginRequest, type RegisterRequest, type User, apiFetch, useAuth };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var g=Object.defineProperty;var w=Object.getOwnPropertyDescriptor;var m=Object.getOwnPropertyNames;var P=Object.prototype.hasOwnProperty;var E=(o,t)=>{for(var i in t)g(o,i,{get:t[i],enumerable:!0})},R=(o,t,i,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of m(t))!P.call(o,s)&&s!==i&&g(o,s,{get:()=>t[s],enumerable:!(a=w(t,s))||a.enumerable});return o};var U=o=>R(g({},"__esModule",{value:!0}),o);var v={};E(v,{AuthContext:()=>A,AuthProvider:()=>$,apiFetch:()=>k,useAuth:()=>S});module.exports=U(v);var c=require("react");async function k(o,t={}){let{method:i="GET",body:a,token:s,onTokenRefresh:h,onAuthFail:f,apiUrl:T="/api",authUrl:y="/auth"}=t,n={"Content-Type":"application/json"};s&&(n.Authorization=`Bearer ${s}`);let d={method:i,headers:n,credentials:"include"};a&&i!=="GET"&&(d.body=JSON.stringify(a));let u=await fetch(`${T}${o}`,d);if(u.status===401&&s&&h)try{let l=await fetch(`${y}/refresh`,{method:"POST",credentials:"include"});if(l.ok){let r=(await l.json()).accessToken;h(r),n.Authorization=`Bearer ${r}`;let p=await fetch(`${T}${o}`,{...d,headers:n});if(!p.ok)throw new Error(await p.text());return p.json()}else throw f?.(),new Error("Session expired")}catch(l){throw f?.(),l}if(!u.ok){let l=await u.text(),e="An error occurred";try{let r=JSON.parse(l);e=r.message||r||e}catch{e=l||e}throw new Error(e)}return u.json()}var x=require("react/jsx-runtime"),A=(0,c.createContext)({accessToken:null,setAccessToken:()=>{},user:null,setUser:()=>{},isLoading:!0,checkAuth:async()=>!1,logout:async()=>{},fetch:async()=>{throw new Error("AuthContext.fetch not implemented")}}),$=({children:o,authUrl:t,apiUrl:i})=>{let[a,s]=(0,c.useState)(null),[h,f]=(0,c.useState)(null),[T,y]=(0,c.useState)(!0);(0,c.useEffect)(()=>{(async()=>(await u(),y(!1)))()},[]);let n=(e,r)=>{s(e),f(r)},d=async(e,r={})=>k(e,{...r,token:a,apiUrl:i||"",authUrl:t,onTokenRefresh:p=>{n(p,h)},onAuthFail:()=>{n(null,null)}}),u=async()=>{try{let e=await fetch(`${t}/refresh`,{method:"POST",credentials:"include"});if(!e.ok)return n(null,null),!1;let r=await e.json();return r.success&&r.accessToken&&r.user?(n(r.accessToken,r.user),!0):(n(null,null),!1)}catch{return n(null,null),!1}},l=async()=>{try{await fetch(`${t}/logout`,{method:"POST",credentials:"include",headers:a?{Authorization:`Bearer ${a}`}:{}})}catch(e){console.error("Logout failed:",e)}finally{n(null,null)}};return(0,x.jsx)(A.Provider,{value:{accessToken:a,setAccessToken:e=>n(e,h),user:h,setUser:f,isLoading:T,checkAuth:u,logout:l,fetch:d},children:o})},S=()=>(0,c.useContext)(A);0&&(module.exports={AuthContext,AuthProvider,apiFetch,useAuth});
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{createContext as A,useContext as x,useState as y,useEffect as w}from"react";async function g(f,c={}){let{method:d="GET",body:o,token:i,onTokenRefresh:a,onAuthFail:l,apiUrl:p="/api",authUrl:T="/auth"}=c,r={"Content-Type":"application/json"};i&&(r.Authorization=`Bearer ${i}`);let u={method:d,headers:r,credentials:"include"};o&&d!=="GET"&&(u.body=JSON.stringify(o));let s=await fetch(`${p}${f}`,u);if(s.status===401&&i&&a)try{let n=await fetch(`${T}/refresh`,{method:"POST",credentials:"include"});if(n.ok){let t=(await n.json()).accessToken;a(t),r.Authorization=`Bearer ${t}`;let h=await fetch(`${p}${f}`,{...u,headers:r});if(!h.ok)throw new Error(await h.text());return h.json()}else throw l?.(),new Error("Session expired")}catch(n){throw l?.(),n}if(!s.ok){let n=await s.text(),e="An error occurred";try{let t=JSON.parse(n);e=t.message||t||e}catch{e=n||e}throw new Error(e)}return s.json()}import{jsx as m}from"react/jsx-runtime";var k=A({accessToken:null,setAccessToken:()=>{},user:null,setUser:()=>{},isLoading:!0,checkAuth:async()=>!1,logout:async()=>{},fetch:async()=>{throw new Error("AuthContext.fetch not implemented")}}),$=({children:f,authUrl:c,apiUrl:d})=>{let[o,i]=y(null),[a,l]=y(null),[p,T]=y(!0);w(()=>{(async()=>(await s(),T(!1)))()},[]);let r=(e,t)=>{i(e),l(t)},u=async(e,t={})=>g(e,{...t,token:o,apiUrl:d||"",authUrl:c,onTokenRefresh:h=>{r(h,a)},onAuthFail:()=>{r(null,null)}}),s=async()=>{try{let e=await fetch(`${c}/refresh`,{method:"POST",credentials:"include"});if(!e.ok)return r(null,null),!1;let t=await e.json();return t.success&&t.accessToken&&t.user?(r(t.accessToken,t.user),!0):(r(null,null),!1)}catch{return r(null,null),!1}},n=async()=>{try{await fetch(`${c}/logout`,{method:"POST",credentials:"include",headers:o?{Authorization:`Bearer ${o}`}:{}})}catch(e){console.error("Logout failed:",e)}finally{r(null,null)}};return m(k.Provider,{value:{accessToken:o,setAccessToken:e=>r(e,a),user:a,setUser:l,isLoading:p,checkAuth:s,logout:n,fetch:u},children:f})},S=()=>x(k);export{k as AuthContext,$ as AuthProvider,g as apiFetch,S as useAuth};
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@zhmdff/auth-react",
3
+ "version": "1.0.0",
4
+ "description": "Plug and play authentication library for React/Next.js",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts --minify --clean",
13
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
14
+ "lint": "eslint src"
15
+ },
16
+ "peerDependencies": {
17
+ "react": ">=18",
18
+ "react-dom": ">=18",
19
+ "next": ">=13"
20
+ },
21
+ "devDependencies": {
22
+ "tsup": "^8.0.2",
23
+ "typescript": "^5.3.3",
24
+ "@types/react": "^18.2.48",
25
+ "@types/react-dom": "^18.2.18"
26
+ }
27
+ }