Start learning free

LESSON 173 OF 173 · BACKEND: YOUR OWN API

Short Links: Delete with a Key

Practice

A link's creator should be able to delete it, and nobody else. Codes count 1, 2, 3, so anyone can guess them: knowing a code proves nothing. The proof is a key, a random secret the app makes with each new link and sends only to its creator, in the 201 answer. To delete, the client sends that key back in a request header named X-Owner-Key.

C#'s RandomNumberGenerator makes random bytes nobody can guess, Convert.ToHexString turns them into text, and a dictionary's Remove takes out a key and its value:

using System.Security.Cryptography;
var token = Convert.ToHexString(RandomNumberGenerator.GetBytes(8)).ToLower();   // 16 hex characters, like "4f0c9a7e21b3d865"
var scores = new Dictionary<string, int> { ["ann"] = 3, ["bob"] = 5 };
scores.Remove("ann");   // scores now holds only "bob" = 5

A route reads headers through an HttpRequest parameter: request.Headers[...].ToString() gives the header's text, or "" when the header is missing. Here is a header check on another route:

app.MapGet("/admin/report", (HttpRequest request) => {
    if (request.Headers["X-Admin-Token"].ToString() != adminToken) {
        return Results.Json(new { error = "Not allowed" }, statusCode: 403);
    }
    return Results.Json(new { links = links.Count });
});

403 Forbidden means "you may not do this". A missing key and a wrong key get the same 403: there are no accounts to log in to. A delete that works answers 204 No Content, with an empty body. These are the test requests, in order (<key>, <key2> and <key3> stand for the random keys of links 1, 2 and 3):

POST /links  {"url": "https://www.python.org/downloads/"}  →  201  {"code": "1", "url": "https://www.python.org/downloads/", "key": "<key>"}
POST /links  {"url": "https://en.wikipedia.org/wiki/URL_shortening"}  →  201  {"code": "2", "url": "https://en.wikipedia.org/wiki/URL_shortening", "key": "<key2>"}
POST /links  {"url": "https://www.python.org/downloads/"}  →  200  {"code": "1", "url": "https://www.python.org/downloads/"}
DELETE /links/1  (no key)  →  403  {"error": "Wrong or missing key"}
DELETE /links/1  X-Owner-Key: <key2>  →  403  {"error": "Wrong or missing key"}
DELETE /links/1  X-Owner-Key: <key>  →  204  (empty body)
GET /1  →  404  {"error": "Link not found"}
DELETE /links/1  X-Owner-Key: <key>  →  404  {"error": "Link not found"}
GET /links/2/stats  →  200  {"code": "2", "url": "https://en.wikipedia.org/wiki/URL_shortening", "clicks": 0}
POST /links  {"url": "https://flask.palletsprojects.com/en/stable/quickstart/"}  →  201  {"code": "3", "url": "https://flask.palletsprojects.com/en/stable/quickstart/", "key": "<key3>"}

Your task: in the /links route, make a key from RandomNumberGenerator.GetBytes(8) as lower-case hex text, store it with the new link and add it to the 201 answer. Then add app.MapDelete for /links/{code}: 404 for an unknown code, 403 when the X-Owner-Key header isn't that link's key, and otherwise remove the link and answer an empty 204.


Assignment
Follow the Coding Tutorial to give every new link a secret key and to add the DELETE route that checks it.


Hint
Check the code before the key: for an unknown code, links[code]["key"] would throw. The answer for a URL that was already shortened stays without the key. Results.StatusCode(204) answers with an empty body. C# compiles a route only when every path through it returns something, so the app won't run until the last step is in.


STEP 1 OF 10
    Ctrl + Enter to check