- 새로고침 시 로그인 해제 오류 해결 및 자동로그인 기능 구현
This commit is contained in:
geonhee-min
2025-12-03 12:59:08 +09:00
parent 3859099074
commit e3091494b1
4 changed files with 108 additions and 39 deletions

View File

@@ -7,19 +7,39 @@ import { PageRouting } from './const/PageRouting';
import LoginPage from './ui/page/account/login/LoginPage'; import LoginPage from './ui/page/account/login/LoginPage';
import ResetPasswordPage from './ui/page/account/resetPassword/ResetPasswordPage'; import ResetPasswordPage from './ui/page/account/resetPassword/ResetPasswordPage';
import { HomePage } from './ui/page/home/HomePage'; import { HomePage } from './ui/page/home/HomePage';
import type { AuthData } from './data/AuthData';
import { useEffect } from 'react';
function App() { function App() {
const { authData } = useAuthStore(); const { authData, login } = useAuthStore();
useEffect(() => {
const autoLogin = localStorage.getItem('autoLogin') === 'true';
if (autoLogin) {
const stored = localStorage.getItem('auth-storage');
if (stored) {
const storedAuthData = JSON.parse(stored).state as AuthData;
login(storedAuthData);
}
}
}, []);
return ( return (
<Router> <Router>
<Routes> <Routes>
<Route element={<Layout />}> <Route element={<Layout />}>
{
!authData
? <>
<Route element={<LoginPage />} path={PageRouting["LOGIN"].path} /> <Route element={<LoginPage />} path={PageRouting["LOGIN"].path} />
<Route element={<SignUpPage />} path={PageRouting["SIGN_UP"].path} /> <Route element={<SignUpPage />} path={PageRouting["SIGN_UP"].path} />
<Route element={<ResetPasswordPage />} path={PageRouting["RESET_PASSWORD"].path} /> <Route element={<ResetPasswordPage />} path={PageRouting["RESET_PASSWORD"].path} />
{!authData ? <Route element={<Navigate to={PageRouting["LOGIN"].path} />} path="*" /> : null} <Route element={<Navigate to={PageRouting["LOGIN"].path} />} path="*" />
</>
: <>
<Route element={<Navigate to={PageRouting["HOME"].path} />} path="*" />
<Route element={<HomePage />} path={PageRouting["HOME"].path} /> <Route element={<HomePage />} path={PageRouting["HOME"].path} />
</>
}
</Route> </Route>
</Routes> </Routes>
</Router> </Router>

View File

@@ -7,7 +7,8 @@ import type {
InternalAxiosRequestConfig, InternalAxiosRequestConfig,
} from "axios"; } from "axios";
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import type { RefreshAccessTokenResponse } from '@/data/response/account/RefreshAccessTokenResponse'; import { RefreshAccessTokenResponse } from '@/data/response/account/RefreshAccessTokenResponse';
import type { AuthData } from '@/data/AuthData';
export class BaseNetwork { export class BaseNetwork {
protected instance: AxiosInstance; protected instance: AxiosInstance;
@@ -98,30 +99,16 @@ export class BaseNetwork {
this.isRefreshing = true; this.isRefreshing = true;
try { try {
const response = await this.get<RefreshAccessTokenResponse>( await this.refreshToken();
'/account/refresh-access-token',
{
headers: {
Authorization: `Bearer ${refreshToken}`
}
}
)
const newAccessToken = response.data.accessToken; const newAccessToken = useAuthStore.getState().authData!.accessToken;
const newRefreshToken = response.data.refreshToken;
useAuthStore.getState().login({
...authData,
accessToken: newAccessToken,
refreshToken: newRefreshToken
});
this.refreshQueue.forEach((cb) => cb(newAccessToken)); this.refreshQueue.forEach((cb) => cb(newAccessToken));
this.refreshQueue = []; this.refreshQueue = [];
originalRequest.headers = { originalRequest.headers = {
...originalRequest.headers, ...originalRequest.headers,
Authorization: `Bearer ${newAccessToken}`, Authorization: `Bearer ${newAccessToken}`,
}; } as any;
return this.instance(originalRequest); return this.instance(originalRequest);
} catch (err) { } catch (err) {
@@ -143,4 +130,39 @@ export class BaseNetwork {
protected async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig & { authPass?: boolean }) { protected async post<T = any>(url: string, data?: any, config?: AxiosRequestConfig & { authPass?: boolean }) {
return await this.instance.post<T>(url, data, config); return await this.instance.post<T>(url, data, config);
} }
public async refreshToken() {
const storedAuth = localStorage.getItem('auth-storage');
if (!storedAuth) {
localStorage.setItem('autoLogin', 'false');
throw new Error;
}
const authData: AuthData = JSON.parse(storedAuth).state;
if (!authData || !authData.refreshToken) {
localStorage.setItem('autoLogin', 'false');
throw new Error;
}
const result = await this.get<RefreshAccessTokenResponse>(
'/account/refresh-access-token',
{
headers: {
Authorization: `Bearer ${authData.refreshToken}`
}
}
);
if (!result.data.success) throw new Error;
const newAccessToken = result.data.accessToken;
const newRefreshToken = result.data.refreshToken;
useAuthStore.getState().login({
accessToken: newAccessToken,
refreshToken: newRefreshToken
});
}
} }

View File

@@ -1,5 +1,6 @@
import type { AuthData } from '@/data/AuthData'; import type { AuthData } from '@/data/AuthData';
import { create } from 'zustand'; import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
interface AuthStoreProps { interface AuthStoreProps {
authData: AuthData | undefined; authData: AuthData | undefined;
@@ -7,17 +8,23 @@ interface AuthStoreProps {
logout: () => void; logout: () => void;
} }
export const useAuthStore = create<AuthStoreProps>((set) => ({ const storage = sessionStorage;
export const useAuthStore = create<AuthStoreProps>()(
persist(
(set) => ({
authData: undefined, authData: undefined,
login: (data: AuthData) => { login: (data: AuthData) => {
set({ authData: data }); set({ authData: data });
Object.entries(data)
.forEach((entry) => {
localStorage.setItem(entry[0], entry[1]);
})
}, },
logout: () => { logout: () => {
localStorage.setItem('autoLogin', 'false');
localStorage.removeItem('auth-storage');
set({ authData: undefined }); set({ authData: undefined });
localStorage.clear();
} }
})); }),
{
name: 'auth-storage',
storage: createJSONStorage(() => storage)
}
)
);

View File

@@ -4,7 +4,7 @@ import { Field, FieldError, FieldLabel } from '@/components/ui/field';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import React, { useCallback, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { PageRouting } from '@/const/PageRouting'; import { PageRouting } from '@/const/PageRouting';
@@ -15,9 +15,12 @@ import { LoginRequest } from '@/data/request/account/LoginRequest';
import { AccountNetwork } from '@/network/AccountNetwork'; import { AccountNetwork } from '@/network/AccountNetwork';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
export default function LoginPage() { export default function LoginPage() {
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [autoLogin, setAutoLogin] = useState<boolean>(false);
const { login } = useAuthStore(); const { login } = useAuthStore();
const navigate = useNavigate(); const navigate = useNavigate();
const accountNetwork = new AccountNetwork(); const accountNetwork = new AccountNetwork();
@@ -30,6 +33,10 @@ export default function LoginPage() {
}); });
const { id, password } = { id: loginForm.watch('id'), password: loginForm.watch('password') }; const { id, password } = { id: loginForm.watch('id'), password: loginForm.watch('password') };
useEffect(() => {
localStorage.setItem('autoLogin', `${autoLogin}`)
}, [autoLogin]);
const moveToSignUpPage = useCallback(() => { const moveToSignUpPage = useCallback(() => {
navigate(PageRouting["SIGN_UP"].path); navigate(PageRouting["SIGN_UP"].path);
}, []); }, []);
@@ -149,7 +156,20 @@ export default function LoginPage() {
)} )}
> >
</Controller> </Controller>
<div className="flex flex-row gap-2 mt-2">
<Checkbox
className={[
"data-[state=checked]:bg-indigo-500 data-[state=checked]:text-white"
, "data-[state=checked]:outline-none data-[state=checked]:border-0"
].join(' ')}
id="auto-login"
checked={autoLogin}
onCheckedChange={(value) => setAutoLogin(value === true)}
/>
<Label htmlFor="auto-login"> </Label>
</div>
</form> </form>
</CardContent> </CardContent>
<CardFooter <CardFooter
className="w-full flex flex-col items-center gap-5" className="w-full flex flex-col items-center gap-5"