This episode covers authentication in Remix: session handling with cookies, form-based login patterns with actions, route protection with loaders guards, and OAuth and social login using remix-auth.

Every application that stores user data eventually has to answer one question: who is using this application right now? The answer is called authentication, and Remix handles it as consistently as everything else — through HTTP requests and responses.
Remix's authentication model is simple yet powerful. After a successful login, an action stores the user's identity in a session kept in a cookie. On every subsequent request, a loader reads that cookie to recognize the user. There's no global state in memory — everything runs through standard web mechanisms.
Episode 11 builds authentication from scratch: session cookies, a login form, route protection, then OAuth and social login integration with remix-auth.
Session storage is built with createCookieSessionStorage from @remix-run/node. It stores session data in an encrypted cookie — its contents can't be read by users.
import { createCookieSessionStorage } from "@remix-run/node";
export const sessionStorage = createCookieSessionStorage({
cookie: {
name: "__session",
httpOnly: true,
sameSite: "lax",
secrets: [process.env.SESSION_SECRET],
secure: process.env.NODE_ENV === "production",
},
});The secrets on the cookie are used to sign the session contents. httpOnly prevents access from JavaScript, and secure ensures the cookie only travels over HTTPS in production.
Sessions are read with getSession and modified through the set and get methods. Changes only take effect after commitSession returns a cookie that is set in the response headers.
The most basic model: a login form is submitted to an action, the action verifies the credentials, then stores the user id in the session:
import { redirect } from "@remix-run/node";
import { sessionStorage } from "~/lib/session.server";
export async function action({ request }) {
const formData = await request.formData();
const email = String(formData.get("email"));
const password = String(formData.get("password"));
const user = await verifikasiKredensial(email, password);
if (!user) return { error: "Email atau password salah" };
const session = await sessionStorage.getSession(
request.headers.get("Cookie"),
);
session.set("userId", user.id);
return redirect("/dasbor", {
headers: {
"Set-Cookie": await sessionStorage.commitSession(session),
},
});
}commitSession produces a Set-Cookie header that the browser stores. After this, every request carries the session cookie and the user's identity can be read.
Never store plain passwords. Use a hash function designed for passwords, such as bcrypt or argon2. Verification compares hashes, not plain strings. Libraries like bcryptjs are easy to integrate into loaders and actions.
Route protection happens in the loader: read the session, and if there's no user, redirect to the login page. Because loaders run on the server, protected pages are never rendered without permission.
export async function loader({ request }) {
const session = await sessionStorage.getSession(
request.headers.get("Cookie"),
);
const userId = session.get("userId");
if (!userId) return redirect("/login");
return { userId };
}This guard becomes a habit: every private route starts its loader with a session check. To avoid repetition, create a shared requireUser(request) helper in a common module — this pattern is cleaned up further in episode 18.
Because the session only holds an id, loaders usually fetch the user's data from the database and return it to components. For a user needed in many routes, consider loading it in the root layout and sharing it via React context or a hook.
Building OAuth from scratch is error-prone. The remix-auth library provides strategies for Google, GitHub, and many other providers. The concept: the app redirects the user to the provider, the provider sends back a code, and the strategy exchanges that code for a user profile.
npm install remix-auth remix-auth-googleInstall the provider strategy you need; the concept is uniform across all of them. The flow still follows the Remix pattern: a login action handles the callback, and the session is built once the profile is obtained.
Several things you must pay attention to:
Episode 11 completes authentication end to end: secure session cookies, form-based login with loaders guards, and OAuth and social login via remix-auth. Your application can now recognize its users.
The key takeaways:
In the next episode, episode 12, we'll discuss security best practices — secure headers and content security policy, preventing XSS, CSRF, and injection, server-side input validation and sanitization, and safe secret handling. Authentication opens the door; security guards the whole house.