- Published on
User Authentication With React
- Authors
- Name
- Jacob Toftgaard Rasmussen
For part 2 of the guide we will create a small web app using React and Typescript. It will be very simple and consist of just three components:
App.tsx
LoginForm.tsx
RegisterForm.tsx
When we are done the app will look like this, and I think that the interface talks for itself.
Of course there are many ways that we could make it prettier, but the focus of this article is to establish communication to the API from the previous articles.
Are you ready? Then let's get started!
We can use Vite to quickly get our app scaffolded and running. Don't worry if you have not used Vite before, it is super simple and I will show you what to do. You can also check out the official documentation if you are curious.
Open a terminal and navigate to a directory where you want to create your frontend project. Then if you use NPM
as package manager use the following command:
npm create vite@latest
If you use Yarn
instead then execute the following command:
yarn create vite
In the following prompts, provide your project with a name, choose React as the framework and choose Typescript as the variant.
Next, cd
into the newly created project directory and execute the commands for NPM
users:
npm install
npm run dev
Or these for yarn
users
yarn install
yarn dev
This will start your frontend app, and open up a browser window showing the default template. It should look like this:
The first step is to open up the App.tsx
file inside the src
directory. The goal for the app is to access the secure weather forecast that the ASP.NET Core backend exposes, so let's start by adding the code that will do exactly that. Inside the function called App()
delete everything in between the curly brackets, and exchange it with this code instead:
function App() {
const [weather, setWeather] = useState<weatherType>({
summary: undefined,
temperature: undefined,
});
const getWeather = async () => {
const response = await fetch("http://localhost:5010/weatherforecast", {});
const data = await response.json();
setWeather({
summary: data[0].summary,
temperature: data[0].temperatureC,
});
};
return (
<div className="App">
<div style={{ marginTop: "75px" }}>
<button onClick={getWeather} style={{ marginBottom: "10px" }}>
Get secret weather forecast
</button>
<div>
<div>Weather summary: {weather.summary}</div>
<div>Weather temperature: {weather.temperature}</div>
</div>
</div>
</div>
);
}
You will also need to add this import at the top of the file:
import { useState } from "react";
Now your app should look like this:
Now the App
component includes three things. Firstly, we have a useState
statement that creates a variable called weather
and a function called setWeather
. weather
will store the state of the weather forecast, and at the beginning it will simply be undefined
. setWeather
is a function that will allow us to assign weather
with a new state.
Secondly, we have another function called getWeather
. This function will make an asynchronous fetch call to our API's weatherforecast
endpoint, (remember to set the port such that it matches your local setup). Once it has received a response from the backend, we will store the weather forecast in the weather
variable using the setWeather
function.
Thirdly, we have the HTML that the App
component consists of. Here we have a button with an onClick
attribute on it, which will call the getWeather
function. Then we also have two div tags that just display the current state of the weather
variable.
If you open up the developer console in your browser, and then click on the button, you should receive a 401
error response from the API. That's good, because that means that our authentication for the weather forecast endpoint is working.
Creating forms for registration and for signing in
The next step is to provide users some way of signing up a new account, and logging in to an existing account. To do this, we will create two new components: RegisterForm.tsx
and LoginForm.tsx
. RegisterForm.tsx
looks like this:
import { useState } from "react";
const RegisterForm = () => {
const [email, setEmail] = useState<string>("");
const [username, setUsername] = useState<string>("");
const [password, setPassword] = useState<string>("");
type RegistrationRequest = {
email: string;
username: string;
password: string;
};
const registerUser = async () => {
if (email && username && password) {
const regRequest: RegistrationRequest = {
email,
username,
password,
};
const response = await fetch("http://localhost:5010/auth/register", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(regRequest),
});
const data = await response.json();
console.log(data);
}
};
return (
<div>
<h1>Register</h1>
<form action="#">
<div>
<input
type="email"
name="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<input
type="username"
name="username"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<div>
<input
type="password"
name="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div>
<button
type="button"
onClick={registerUser}
style={{ marginTop: "10px" }}
>
Submit
</button>
</div>
</form>
</div>
);
};
export default RegisterForm;
There is nothing fancy going on here, we just have a form tag with three input tags inside of it. Similarly to the weather
variable that we saw previously in the App.tsx
component, we also use the useState
hook to create state variables for the email
, username
and password
variables. The function called registerUser
first checks that the email
, username
and password
are all present and if they are then it sends a fetch request to the API's registration
endpoint. That function is triggered when we click the Submit
button because of the onClick
attribute that we put on the button. We also define a type for the RegistrationRequest
for type safety.
The next file is the LoginForm.tsx
and it looks like this:
import { useState } from "react";
const LoginForm = () => {
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
type LoginRequest = {
email: string;
password: string;
};
const login = async () => {
if (email && password) {
const loginRequest: LoginRequest = {
email,
password,
};
const response = await fetch("http://localhost:5010/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(loginRequest),
});
const data = await response.json();
}
};
return (
<div className="LoginForm">
<h1>Login</h1>
<form action="#">
<div>
<input
type="email"
name="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
<div>
<input
type="password"
name="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div>
<button type="button" onClick={login} style={{ marginTop: "10px" }}>
Login
</button>
</div>
</form>
</div>
);
};
export default LoginForm;
This component is so similar to the RegisterForm
that I will not explain it. Take a look at the code, and I am sure that you understand everything going on here.
Next, let's use these two components inside of the App
component, so open up App.tsx
and add the following code:
<RegisterForm />
<LoginForm />
Just inside the div tag with the className="App"
.
Great! The interface of the your app should now look like the initial picture in this guide. Try to open up the developer console and go to the network tab, then first try to register a new user, and afterwards login with that user, (you need to have the backend API running as well as the docker container with the PostgreSQL database). In the network tab you should see two successful calls. The first has a response 201
because it created a new user, and the second has a response 200
also indicating a successful request. If you further inspect the second response, by clicking on it, you will find the JWT access token that the user can use to request the weather forecast. It should look like the screenshot below:
Using the access token in the weather forecast request
The final step that ties it all together is to use the access token when we make calls to the weather forecast. Currently, the token is not taken care of when we receive it after logging in. To fix this we will create a new state variable inside App.tsx
. Just above the declaration of weather
and setWeather
add the following line:
const [token, setToken] = useState<string>("");
This variable will hold the token, and initially it will just be an empty string. When we receive the access token after logging in, then we will use the setToken
function to overwrite the token
variable with the new token. In order to do this when we receive the login response we have to pass setToken
to the Login
component as part of its props. First, let's add the needed code to Login.tsx
. Add a parameter to the LoginForm
function such that it looks like this:
const LoginForm = (props: {
setToken: React.Dispatch<React.SetStateAction<string>>
}) => {
This specifies that we need to provide the setToken
function when we use the LoginForm
component. Also, inside the function called login
just after the line with const data = await response.json()
, add the following line of code:
props.setToken(data.token);
Then inside App.tsx
you need to pass the setToken
function to the LoginForm
component like this:
<LoginForm setToken={setToken} />
Finally, we just need to use the token in our request to the weather API. Also in App.tsx
find the getWeather
function. We need to add authorization headers to the fetch
request. Exchange the line:
const response = await fetch("http://localhost:5010/weatherforecast", {});
With this code:
const response = await fetch("http://localhost:5010/weatherforecast", {
headers: {
Authorization: `Bearer ${token}`,
},
});
That is it!
Now click on the Get secret weather forecast
button, and you should get the weather forecast just like this:
Congratulations on getting this far! I hope you enjoyed this full stack guide, and that it was useful for you.