We start off on the frontend, where we submit some data.
frontend – src/pages/Register.jsx
1 2 3 4 5 |
const onSubmit = (e) => { // other code dispatch(register(userData)) // HERE } } |
we dispatch register with userData, via createAsyncThunk. createAsyncThunk creates an async request. It generates promise lifecycle action types pending/fulfilled/rejected.
We start the process by defining a payload creator, which is the anon function provided to createAsyncThunk. It tries to execute our authServer.register function.
frontend – src/features/auth/authSlice.js
1 2 3 4 5 6 7 |
export const register = createAsyncThunk( 'auth/register', async (user, thunkAPI) => { try { return await authService.register(user) // HERE } catch(...) { } ) |
This allows us to use axios’s post and hit the URL with the body data.
frontend – src/features/auth/authService.js
1 2 3 4 5 |
const register = async (userData) => { // other code const response = await axios.post(process.env.API_URL, userData) // HERE // other code } |
The request is received on the backend.
When we request to register a user, Node receives the request and data in the body.
If the username doesn’t exist, it would create the user, and then generate a token.
It would return the newly created user data and the generated token back to the front end.
backend – backend/controllers/userController.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
const registerUser = asyncHandler(async (req, res) => { const { username, password } = req.body // other code const user = await User.create({ username, password: hashedPassword, }) if (user) { res.status(201).json({ // HERE _id: user.id, username: user.username, token: generateToken(user._id), }) } } |
In order to generate the token, it would take the user’s id, the JWT_SECRET string, and create a token by using the sign function.
1 2 3 4 5 6 |
// Generate JWT const generateToken = (id) => { return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '30d', }) } |
JWTs can be signed using a secret (in our case JWT_SECRET string) with the HMAC algorithm.
It will spit back a long jwt token string.
This string can only be decrypted with the same string.
The reason why we do this is because we want the front end to use the same token and send it back to use, which needs to be decrypted by using the same JWT_SECRET:
backend/middleware/authMiddleware.js
1 |
const decoded = jwt.verify(token, process.env.JWT_SECRET) |
We can then get the user’s id and find it in our database. This ensures that the client making the request is someone we have dealt with before.
Newly created user and token arrives in React
1 2 3 4 5 6 7 8 9 |
const register = async (userData) => { // FETCHED DATA RETURNS HERE const response = await axios.post(process.env.API_URL, userData) // other code return response.data // RETURNS THE FETCHED DATA } |
we return the data to the async anon function that is the 2nd parameter of createAsyncThunk:
1 2 3 4 5 6 7 8 |
export const register = createAsyncThunk( 'auth/register', async (user, thunkAPI) => { try { return await authService.register(user) // HERE } catch (error) { ...} } ) |
This will return to our state’s user object via action.payload when our register thunk’s promise has been fullfilled:
1 2 3 4 5 |
.addCase(register.fulfilled, (state, action) => { state.isLoading = false state.isSuccess = true state.user = action.payload }) |
This is how our thunkAPI’s can access the token from its user object in the state.
The thunk can access the store (getState) and accesses its reducer auth, which then accesses its state property user, and finally, the token property.
The user comes from the state:
And the auth is the reducer’s property name in the store: