Authentication
Learning objectives
- You know what the term authentication means.
- You know of password-based and session-based authentication.
Authentication
The term authentication refers to identifying a user. Users can be identified through multiple means, including asking about something they know, asking for something they have, or checking for something they are. In the context of web applications, passwords are often used for identification -- passwords are an instance of something the users know. Asking for something they have could involve a user's mobile phone being sent a message or a code, checking that the mobile phone is at the user's disposal. Similarly, checking for something they are could involve biometric authentication, such as using iris or fingerprint recognation, or for example checking the identity of the user through keystroke dynamics.
The term 2-factor authentication refes to using at least two means to identify the user, e.g. checking for a password and assessing whether the user has their phone by sending a code to the phone. In the context of this course, we rely on simpler means, i.e. password-based authentication.
Simple authentication
A very crude application for authentication could be as follows. The following application expects that a password is sent in the request body. If the user sends a password "very secret password", then the user is shown a message "You knew the password, you are authenticated!". Otherwise, the user is sent the HTTP status code 401, indicating bad credentials or unauthorized access, and the text "Invalid password, you are not authenticated!".
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
const app = new Hono();
app.get("/", (c) =>
c.html(`<form method="POST" action="/authenticate">
<input type="password" name="password" />
<input type="submit" />
</form>`));
app.post("/authenticate", async (c) => {
const body = await c.req.parseBody();
if (body.password === "very secret password") {
return c.text("You knew the password, you are authenticated!");
}
return c.text("Invalid password, you are not authenticated!", 401);
});
Deno.serve(app.fetch);
When we launch the application, we observe that it works as expected. With an incorrect password, we receive the status code 401 and the response text "Invalid password, you are not authenticated!". With the correct password, we receive the status code 200 and the response text "You knew the password, you are authenticated!".
curl -v -X POST -d "password=hamburger" localhost:8000/authenticate
...
< HTTP/1.1 401 Unauthorized
...
Invalid password, you are not authenticated!%
curl -v -X POST -d "password=very secret password" localhost:8000/authenticate
...
< HTTP/1.1 200 OK
...
You knew the password, you are authenticated!%
While the above application demonstrates authentication, it is not very useful. Now, while this demonstrates authentication, it does not really get us far. If we would use the above approach, we would have to send the password to the server on each request.
Plaintext passwords
The examples here demonstrate the use of plaintext (and hard-coded) passwords, where the passwords are stored on the server in an easily readable format. This is not a good practice, and we use the plaintext passwords to demonstrate the authentication concepts. We will later look into how passwords should really be stored.
Session-based authentication
Session-based authentication is based -- as the name implies -- on the use of sessions. When the user sends the credentials, e.g. a password, to the server, the server verifies the credentials. If the credentials are correct, a session is created and stored on the server. The server then sends a session identifier to the user as a cookie. Then, on subsequent requests, the user sends the session identifier to the server.
In a very simple form, this could work as follows. Theapplication has a form for entering a password. When the user sends an incorrect password, the user receives a message telling that the password was incorrect. However, when the user sends the correct password, the server creates a session and sends the session identifier to the user as a cookie, redirecting the user to a new page. Then, on the new page, the server verifies whether the session exists before allowing the user to see the content.
import { Hono } from "https://deno.land/x/hono@v3.12.11/mod.ts";
import {
getSignedCookie,
setSignedCookie,
} from "https://deno.land/x/hono@v3.12.11/helper.ts";
const app = new Hono();
const secret = "secret";
const sessions = new Set();
app.get("/", (c) =>
c.html(`<form method="POST" action="/authenticate">
<input type="password" name="password" />
<input type="submit" />
</form>`));
app.post("/authenticate", async (c) => {
const body = await c.req.parseBody();
if (body.password === "very secret password") {
const sessionId = crypto.randomUUID();
await setSignedCookie(c, "sessionId", sessionId, secret, {
path: "/",
});
sessions.add(sessionId);
return c.redirect("/secrets");
}
return c.text("Invalid password, you are not authenticated!", 401);
});
app.get("/secrets", async (c) => {
const sessionId = await getSignedCookie(c, secret, "sessionId");
if (!sessionId || !sessions.has(sessionId)) {
return c.text("You have not authenticated!", 401);
}
return c.text("Hello session-based authentication!");
});
Deno.serve(app.fetch);
The following example demonstrates how the above application works. We first make a request to the server with a password in the request body. Then, we receive a set-cookie header in the response, which we are expected to send back on each subsequent request. From this point on, when we make a request to the server with the cookie in the request headers, we observe that we are authenticated based on the session.
curl -v -X POST -d "password=hamburger" localhost:8000/authenticate
...
< HTTP/1.1 401 Unauthorized
...
Invalid password, you are not authenticated!%
curl -v -X POST -d "password=very secret password" localhost:8000/authenticate
...
< HTTP/1.1 302 Found
< location: /secrets
< set-cookie: sessionId=cc5aca5e-f30c-4644-b2c6-d6c372443a7e.LdnxseZH%2FxzkW7fmo313%2FSs7Jl1GQ%2B1Q7Eu6heZJZV0%3D
...
curl -v -H "Cookie: sessionId=cc5aca5e-f30c-4644-b2c6-d6c372443a7e.LdnxseZH%2FxzkW7fmo313%2FSs7Jl1GQ%2B1Q7Eu6heZJZV0%3D" localhost:8000/secrets
...
Hello session-based authentication!%
Try it out!
Naturally, not any session identifier will be accepted. Try out the above program by first trying to access the path "/secrets" without a session identifier, and then with an incorrect session identifier.