thecodex.expert · The Codex Family of Knowledge
Tier 4 · Advanced · Python Project

REST API with Authentication

Add token-based authentication to a REST API: register, log in, and protect routes so only logged-in users get through. Real security fundamentals.

🧠 Teaches how to think spoonfed, every age Last verified:

1 The Problem

We want to secure an API: users register with a password, log in to get a token, and must send that token to access protected routes. We will hash passwords properly and issue tokens. It teaches authentication — how real APIs verify who you are without storing passwords in plain text.

Where this shows up: every app with logins — every SaaS, every mobile backend, every dashboard. Getting auth right (hashed passwords, token checks) is non-negotiable; getting it wrong is how data breaches happen.

2 How to Think About It

Think about proving identity safely, before any code:

The plan — in plain English
1. Register: store the username and a hash of the password — never the password itself. → 2. Log in: hash the supplied password and compare to the stored hash; if it matches, issue a random token. → 3. Protected routes: require a valid token, look up who it belongs to. → The password is never stored or sent in readable form.

Match

No match

Yes

No

Register: username + password

Store username + password HASH

Login: username + password

Hash and compare

Issue a random token

Reject

Protected request + token

Valid token?

Allow, know the user

401 Unauthorized

3 The Build — explained part by part

Here is the complete auth core. Each part is explained below.

Pythonauth.py
import hashlib
import secrets

class Auth:
    def __init__(self):
        self.users = {}      # username -> password_hash
        self.tokens = {}     # token -> username

    def _hash(self, password):
        # Hash the password so we never store it in plain text.
        return hashlib.sha256(password.encode()).hexdigest()

    def register(self, username, password):
        if username in self.users:
            raise ValueError("Username taken")
        self.users[username] = self._hash(password)

    def login(self, username, password):
        stored = self.users.get(username)
        if stored is None or stored != self._hash(password):
            raise ValueError("Invalid credentials")
        token = secrets.token_hex(16)      # random, unguessable
        self.tokens[token] = username
        return token

    def whoami(self, token):
        # Return the user for a valid token, or None.
        return self.tokens.get(token)

if __name__ == "__main__":
    auth = Auth()
    auth.register("alice", "hunter2")
    token = auth.login("alice", "hunter2")
    print("Token:", token[:12] + "...")
    print("Authenticated as:", auth.whoami(token))
What each part does — in plain words
_hash(password) — run the password through SHA-256, a one-way hash. We store the hash, never the password. Even if our database leaks, the actual passwords are not in it.

register — store the username with its password hash. Refuse duplicate usernames.

login — hash the supplied password and compare to the stored hash. If they match, the password was correct — without us ever un-hashing anything (you cannot reverse a hash).

secrets.token_hex(16) — issue a random, unguessable token on successful login. The client sends this token on future requests instead of the password.

whoami(token) — look up which user a token belongs to. Protected routes call this; a missing or unknown token means “not logged in”.

note: real systems add salting and use bcrypt/argon2 (slower hashes that resist cracking). This shows the shape; production needs those stronger tools.
Common mistakes — and how to avoid them
✗ Storing passwords in plain text — a catastrophic security failure if leaked.
✓ Always store a hash, never the password itself.
✗ Using a predictable token (like the username) — attackers can forge it.
✓ Use secrets.token_hex for random, unguessable tokens.
✗ Using plain SHA-256 in production — it is too fast and crackable for passwords.
✓ For real systems use bcrypt or argon2 with salting; SHA here shows the shape.

4 Test & Prove Each Part

We test the auth flow: registration, correct and incorrect login, and token validation.

Registering then logging in issues a token
A wrong password is rejected
A valid token identifies the user; an invalid one does not
Pythontest_auth.py
import hashlib
import secrets
import pytest


class Auth:
    def __init__(self):
        self.users = {}
        self.tokens = {}

    def _hash(self, password):
        return hashlib.sha256(password.encode()).hexdigest()

    def register(self, username, password):
        if username in self.users:
            raise ValueError("Username taken")
        self.users[username] = self._hash(password)

    def login(self, username, password):
        stored = self.users.get(username)
        if stored is None or stored != self._hash(password):
            raise ValueError("Invalid credentials")
        token = secrets.token_hex(16)
        self.tokens[token] = username
        return token

    def whoami(self, token):
        return self.tokens.get(token)


def test_register_and_login():
    """Registering then logging in returns a token."""
    auth = Auth()
    auth.register("alice", "pw")
    token = auth.login("alice", "pw")
    assert auth.whoami(token) == "alice"


def test_wrong_password():
    """A wrong password is rejected."""
    auth = Auth()
    auth.register("alice", "pw")
    with pytest.raises(ValueError):
        auth.login("alice", "wrong")


def test_invalid_token():
    """An unknown token identifies no one."""
    auth = Auth()
    assert auth.whoami("fake-token") is None


def test_password_not_stored_plain():
    """The stored value is a hash, not the password."""
    auth = Auth()
    auth.register("alice", "secret")
    assert auth.users["alice"] != "secret"

Run with pytest -v. The last test is the most important security check: it proves the password is not stored in plain text. Testing that sensitive data is protected is as vital as testing that login works.

5 API Documentation

POST/registercreate an account
POST/loginget a token
Response
{"token": "a1b2c3d4..."}
GET/me (needs token)401 without a valid token

6 Run It & Automate It

Run it locally
python3 auth.py
Register, log in, and see a token issued. Combine with the REST API project to protect its routes.

Jenkins runs the tests automatically — each line explained below.

What you should see when it works
Terminala real run
Token: a1b2c3d4e5f6...
Authenticated as: alice
If it breaks — how to fix it
🚨 Login always fails.
Make sure you hash the login password the same way before comparing.
🚨 Tokens stop working after restart.
Tokens are in memory here. Persist them (or use stateless JWTs) for real use.
GroovyJenkinsfile
// Jenkinsfile — automated tests on every change.
pipeline {
    agent any
    stages {
        stage('Get the code') { steps { checkout scm } }       // download the code
        stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } }  // test tool
        stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } }   // run every test
    }
    post {
        success { echo 'All tests passed.' }
        failure { echo 'A test failed — look above.' }
    }
}
🎯 Try this next — make it yours
  1. Salting. Add a random salt per user before hashing. (Teaches: defeating rainbow tables.)
  2. Token expiry. Make tokens expire after a time. (Teaches: session lifetimes.)
  3. JWT. Switch to stateless JSON Web Tokens. (Teaches: modern auth.)
What you learned
You learned authentication: hashing passwords so they are never stored in plain text, verifying by comparing hashes, and issuing random tokens for sessions. This is the security foundation under every app with a login. Related: hashlib, secrets.