Sending Authentication
Objectives
By the end of this chapter, you should be able to:
- Send a token with the
Authorizationheader - Explain the difference between a
401and a403in practice - Explain where a token typically comes from and where it’s commonly stored
💡 Why this matters: Most real APIs aren’t fully public. Almost anything that shows a specific user their own data, their orders, their profile, their messages, needs to know who’s asking before it answers.
⚠️ A note on verification: JSONPlaceholder, this module’s practice API, doesn’t require authentication at all, there’s no real login endpoint to demonstrate against. The pattern below is accurate and extremely common (this exact header shape is used by countless real APIs), but this lesson’s examples use a placeholder domain rather than a live, verifiable one, since no real request was made against it.
The Authorization Header
Authentication is almost always sent as a header on the request, not in the URL and not in the body. The most common shape is the Bearer token.
const response = await fetch("https://api.example-books.com/orders", {
headers: {
Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
},
});
Bearer is a literal word, part of the standard, <space>, then the actual token, usually a long, random-looking string. The server checks this header on every request, decides who’s asking, and either responds normally or rejects the request.
Where the Token Comes From
A token isn’t invented by the client, it comes from the server, typically after a login request.
async function login(email, password) {
const response = await fetch("https://api.example-books.com/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
const data = await response.json();
return data.token;
}
A real login endpoint checks the email and password, and if they’re correct, responds with a token, a string the server itself generated and will recognize on future requests. This course doesn’t cover building that server-side check, only using the token it returns.
Storing the Token
Once you have a token, every subsequent request needs it, which means it has to be saved somewhere between requests. localStorage (Module 5) is the common choice.
async function loginAndSave(email, password) {
const token = await login(email, password);
localStorage.setItem("authToken", token);
}
async function getOrders() {
const token = localStorage.getItem("authToken");
const response = await fetch("https://api.example-books.com/orders", {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.json();
}
Saving the token once at login, then reading it back out of localStorage for every later request, means the user doesn’t have to log in again every time the page reloads, exactly the same reasoning behind saving anything else to localStorage.
401 vs. 403, Revisited
Both status codes (introduced last lesson) relate to authentication, but they mean different things, and reacting to them correctly matters.
const response = await fetch("https://api.example-books.com/orders", {
headers: { Authorization: `Bearer ${token}` },
});
if (response.status === 401) {
console.log("Not logged in, or the token expired. Redirect to login.");
} else if (response.status === 403) {
console.log("Logged in, but not allowed to see this. Don't redirect to login, it won't help.");
}
401 Unauthorized really means “unauthenticated,” no valid token was sent at all, or it expired. The fix is logging in again. 403 Forbidden means the token is valid, the server knows exactly who’s asking, they’re just not allowed to do this specific thing, viewing another user’s private order, for instance. Sending them back to a login screen wouldn’t help, they’re already logged in, correctly, and logging in again changes nothing.
API Keys: A Simpler Alternative
Not every API uses login-based tokens. Some, especially ones meant for automated access rather than an individual logged-in user, use a fixed API key instead, issued once, used on every request.
const response = await fetch("https://api.example-weather.com/forecast?city=London", {
headers: {
"X-API-Key": "your-api-key-here",
},
});
The exact header name varies by API, X-API-Key is common, some use Authorization for this too. Whichever pattern an API uses, its documentation states it explicitly, worth checking before assuming.
Try It
- Write a
fetch()call to"https://api.example-books.com/profile"that sends a token stored in a variable calledauthTokenas a Bearer token. - Write a function
getAuthToken()that reads a token fromlocalStorageunder the key"authToken", and returnsnullif none is saved. - Given a response with
status === 401, and a separate response withstatus === 403, write the different message you’d show a user for each, and explain why redirecting to a login page only makes sense for one of them.
Recap
- Authentication is sent as a header, most commonly
Authorization: Bearer <token>. - A token comes from the server, typically returned by a login request, and is commonly saved to
localStorageso it survives a page reload. 401means not authenticated at all (or an expired token), fix it by logging in again.403means authenticated, but not permitted, logging in again won’t change anything.- Some APIs use a fixed API key in a header instead of a login-based token, the exact header name depends on the specific API’s documentation.
Next lesson: CRUD operations end to end, creating, reading, updating, and deleting the same resource in one connected example.