Introduction
In this article I will be explaining how to handle a basic login that only uses a username
for authentication. Part of the purpose is to be able to mock having a currentUser on the front end without getting into real authentication. For this project we will be using React on the front-end and Sinatra on the server side.
Handling a Successful Login
Setting Up The Basic Route Server-Side
First we're going to set up the route for our server to perform a GET request. Since we're going to be authenticating the user by the username it's quite simple:
In my application the user has_many
trips, which we will want access to later.
get "/users/:username" do
user = User.find_by_username(params[:username])
user.to_json(include: [:trips])
end
Setting up the Login Client-Side
We are going to keep it simple here as well, just a basic input with a submit button. First we're going to set up our basic functions for handling the submit and change for the form data. Don't worry about findCurrentUser
, we'll get to that in a bit.
const [userLogin, setUserLogin] = useState("")
const handleSubmit = (e) => {
e.preventDefault()
findCurrentUser(userLogin)
}
const handleChange = e => {
setUserLogin(e.target.value)
}
Using our state we can set up our form for a very basic login:
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="login" value="Username">Username: </label><br/>
<input type="text" name="login" value={userLogin} onChange={handleChange} autoFocus={true}/>
<input type="submit" value="Login"/>
</form>
</div>
Making the GET Request
Now to set up the fetch, we're going to be using async and await to help our code look a little cleaner and easier to understand:
I'm using the useHistory hook from react-router-dom to redirect the user to their trips page after a successful login
async function findCurrentUser(username) {
const response = await fetch(`${baseUrl}/users/${username}`)
const user = await response.json()
changeUser(user)
history.push(`/users/${user.id}/trips`)
}
}
We are also bringing changeUser
from our App component via props to handle the state of currentUser:
function App() {
const [currentUser, setCurrentUser] = useState(null)
const changeUser = (user) => {
setCurrentUser(user)
}
return (
<Route exact path="/login">
<Login changeUser={changeUser}/>
</Route>
)
}
With all of that set up someone should be able to successfully login!
Success!!!
Failed Login Attempt
Handling Failed Attempt Server-Side
But what happens when someone tries to login, and they don't already have an account?
First let's look at how to handle this error on the server-side. Sinatra has a helper method status
that we are going to take advantage of. It allows us to change the HTTP response status code. 401 is the status code for unauthorized/unauthenticated, which seems to fit what we want to return. So if the user exist, return the user, else change the status code to 401 and return an error message.
get "/users/:username" do
user = User.find_by_username(params[:username])
if user
user.to_json(include: [:trips])
else
status 401
{ errors: "user doesn't exist" }.to_json
end
end
Handling Failed Attempt Client-Side
Now that we are changing the status code on a failed attempt, we can use that to deal with the client side of things. We can use response.status to access the status code, and if it equals 401, do nothing, otherwise perform the actions of a successful login.
async function findCurrentUser(username) {
const response = await fetch(`${baseUrl}/users/${username}`)
if (response.status === 401) {
return null
} else {
const user = await response.json()
changeUser(user)
history.push(`/users/${user.id}/trips`)
}
}
Great! Now we're no longer getting an error when someone has a failed login attempt, but there's one problem, when our user tries to login, it looks like nothing is happening for them. Let's fix that by giving our user an error message.
Displaying an Error message
To handle the display of an error message, we are going to handle it with useState:
const [error, setError] = useState(null)
And we're going to change our fetch just a little bit, instead of just returning null, we're going to set an error message:
async function findCurrentUser(username) {
const response = await fetch(`${baseUrl}/users/${username}`)
if (response.status === 401) {
setError("That user doesn't exist, try again or sign up for an account!")
} else {
const user = await response.json()
changeUser(user)
history.push(`/users/${user.id}/trips`)
}
}
Now that we have our error message all we need to do is display it, and since we are using state and setting it to null on render it will only display when it has been changed from null.
<div>
<form onSubmit={handleSubmit}>
<h3 style={{color:"red"}}>{error}</h3>
<label htmlFor="login" value="Username">Username:</label><br/>
<input type="text" name="login" value={userLogin} onChange={handleChange} autoFocus={true}/>
<input type="submit" value="Login"/>
</form>
</div>
So that it stands out I've put it as an h3 with text color of red.
Conclusion
That's the basics of how to handle both a successful and failed login attempts with just a username, now you can try to figure out how to handle what's going to get displayed based on the current user being logged in or not. Good luck!
Extras
For more information on some of the things I mentioned checkout:
useHistory Hook
Latest comments (0)