I would know how to make user authentication properly. Could you tell me what's the logic behind it?
My environment:
- React with TypeScript
- reduxjs/toolkit
- Node.js with Express
I have implemented this functionality but this is not working as expected. My component are rendering many times and when i refresh the page router navigates me always to the homepage. I know this is wrong approach because at the begining state is always null.
On my app.tsx every time i'm checking if token exists on localStorage, if not then get token and authUser.
useEffect(() => {
if (token) return;
if (getToken()) dispatch(authUser());
}, [dispatch, currentUser, token]);
For my routes i have created ProtectedRouteComponent But in this scenario currentUser is null at the begining (when initial state is null) so router navigates to '/auth' because async thunk need time to fullfill auth.
const ProtectRoute = ({ children }: { children: JSX.Element }) => {
const { currentUser, isFetching } = useAppSelector(state => state.auth);
return isFetching ? <LoadingSpinner /> : currentUser ? children : <Navigate to="/auth" />;
};
SignIn Method
export const signIn = createAsyncThunk<ILoggedUser, IUserCredentials>(
'auth/signIn',
async (userCredentials, thunkAPI) => {
try {
const response: {
status: any;
data: {
token: string;
data: {
id: string;
name: string;
role: string;
avatarUrl: string;
};
};
} = await api.post('/users/login', userCredentials);
if (response.status === 200) {
localStorage.setItem('token', response.data.token);
return response.data.data;
} else {
return thunkAPI.rejectWithValue(response.data);
}
} catch (e) {
console.log('Error: ', e);
return thunkAPI.rejectWithValue(e);
}
}
);
AuthUser Method
export const authUser = createAsyncThunk<IAuthenticatedUser>(
'auth/userAuthentication',
async (_, { rejectWithValue }) => {
try {
const accessToken = getToken();
if (!accessToken) rejectWithValue('Invalid token');
const config = {
headers: { Authorization: `Bearer ${accessToken}` },
};
const response: IAuthUser = await api.get('/users/authenticateUser', config);
const userData = response.data.data.data;
return {
userData,
accessToken,
};
} catch (error) {
removeToken();
return rejectWithValue(error);
}
}
);
AuthSlice
export const authSlice = createSlice({
name: 'auth/signIn',
initialState,
reducers: {
signOut(state) {
state.currentUser = null;
},
},
extraReducers: builder => {
builder.addCase(signIn.pending, state => {
state.isFetching = true;
state.errorMessage = null;
});
builder.addCase(signIn.fulfilled, (state, action) => {
state.isFetching = false;
state.currentUser = action.payload;
});
builder.addCase(signIn.rejected, state => {
state.isFetching = false;
state.errorMessage = 'Niepoprawny adres email lub hasło.';
});
builder.addCase(authUser.pending, state => {
state.isFetching = true;
});
builder.addCase(authUser.fulfilled, (state, action) => {
const { accessToken, userData }: IAuthenticatedUser = action.payload;
state.token = accessToken;
state.currentUser = userData;
state.isFetching = false;
});
builder.addCase(authUser.rejected, state => {
state.token = '';
state.currentUser = null;
state.isFetching = false;
});
builder.addCase(signOut.fulfilled, state => {
state.token = '';
state.currentUser = null;
state.isFetching = false;
});
},
});