Start learning free

LESSON 88 OF 173 · BACKEND: USERS AND SECURITY

Tokens

Practice

HTTP has no memory: every request arrives on its own. After Ana logs in, the next request from Ana's browser carries nothing that says who is asking. The browser could send the password every time, but then the password would travel again and again. Instead, a successful login hands out a token: a random ticket that stands for the user.

The token must be impossible to guess, so it comes from RandomNumberGenerator in System.Security.Cryptography: RandomNumberGenerator.GetBytes(16) gives 16 random bytes, and Convert.ToHexString(…).ToLower() writes them as 32 lowercase hexadecimal characters, a new value on every call. The shop keeps each token in a tokens table next to its user's id, so it can later look up whose ticket it is. The same idea for a coupon code:

var code = Convert.ToHexString(RandomNumberGenerator.GetBytes(4)).ToLower();   // 8 random characters, like "9f1c02ab"
var insert = new SqliteCommand("INSERT INTO coupons (code, percent) VALUES ($code, $percent)", db);
insert.Parameters.AddWithValue("$code", code);
insert.Parameters.AddWithValue("$percent", 10);
insert.ExecuteNonQuery();

The client keeps the token and sends it with each later request, in the header Authorization: Bearer <token> ("bearer": whoever holds the ticket). Reading that header is the next lesson.

The tests first sign up Ana (id 1) and Ben (id 2). <token> stands for a random value that changes on every run, and GET /check/tokens is the grader's look into your table:

POST /login  {"email": "ana@example.com", "password": "secret123"}  →  200  {"token": "<token>"}
POST /login  {"email": "ben@example.com", "password": "tea4two"}    →  200  {"token": "<token2>"}
POST /login  {"email": "ana@example.com", "password": "secret123"}  →  200  {"token": "<token3>"}
POST /login  {"email": "ana@example.com", "password": "secret999"}  →  401  {"error": "Wrong email or password"}
GET /check/tokens  →  200  [{"token": "<token>", "user_id": 1},
                            {"token": "<token2>", "user_id": 2},
                            {"token": "<token3>", "user_id": 1}]

The starting code doesn't compile yet: in /login, the if returns a 401, but nothing is returned after it (error CS1643). Your last line fixes that.

Your task: create a tokens table with the columns token and user_id. In /login, after the 401 check, make a token from 16 random bytes, store it with the user's id, and answer {"token": token}.


Assignment
Follow the Coding Tutorial to create the tokens table and make /login hand out a new token on every successful login.


Hint
Every login adds a new row, even for the same user: Ana logs in twice, so Ana gets two tokens. Use a new SqliteCommand for the INSERT (the reader user still uses cmd), give $user_id the value user["id"], and don't forget insert.ExecuteNonQuery(): without it the row is never saved.


STEP 1 OF 7
    Ctrl + Enter to check