1
Fork 0
rust-crud-actix-mongo-api/README.md
Leonardo Devai 32c15accba Review and modernize all 42 projects to the updated standard
Pinned 2026 toolchains (Go 1.26, Rust 1.98/edition 2024, Python 3.14 + uv, Node 24,
Zig 0.16, NixOS 26.05), postgres 18 / mongo 8, lockfiles built from, non-root
runtimes, .dockerignore, per-project LICENSE, READMEs with the git.devai.io clone
line, checkout@v7 CI. Security fixes in the legacy Rust APIs (any-password login,
self-assigned admin, hard-coded JWT secret), JWT alg/exp/sub enforcement across the
blog series, safe markdown links in the frontends, and many smaller bugs — every
project was built, run and exercised end to end.

Adds scripts/publish.sh + a CI publish job that splits every folder into its own
repo at git.devai.io/templates/<folder>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01128fhuZbgivaSJvtMf4s1G
2026-09-27 21:10:38 +02:00

4.8 KiB

rust-crud-actix-mongo-api

A JWT-secured REST API in Rust with actix-web 4 and MongoDB: bcrypt passwords, access + refresh tokens read from an HttpOnly cookie or an Authorization: Bearer header, a CurrentUser extractor, and role checks (User / Admin) on protected routes.

Run

git clone https://git.devai.io/templates/rust-crud-actix-mongo-api.git
cd rust-crud-actix-mongo-api
docker compose up --build

The API answers on http://localhost:8080 (curl localhost:8080/health → ok). MongoDB keeps its state in ./data/mongo; the unique indexes on users.email and users.username are ensured on every start.

Without Docker: run a MongoDB, export the variables from .env.example, then cargo run (Rust 1.98, the toolchain the Dockerfile pins).

How it works

Method Path Auth Result
GET /health — 200 ok
POST /api/user/create — {email, username, password} → 201 user, 409 if taken
POST /api/auth/login — {email, password} → 200 {email, username, roles, tokens} + token cookie, 401 if wrong
POST /api/auth/refresh — {refresh_token} → 200 new token pair, 401 if used/revoked
GET /api/auth/validate any role 200 current user, 401 without a valid token
GET /api/user/{username} any role 200 user — yourself, or anyone if you are Admin (403 otherwise)
GET /api/public optional 200 {username, endpoint_security}, username is null when anonymous
GET /api/protected/user any role 200, or 401
GET /api/protected/admin Admin 200, 401 without a token, 403 for a plain User
  • Tokens — login returns an access token (15 min) and a refresh token (7 days), both HS256 JWTs signed with AUTH_SECRET. Verification pins HS256 and requires exp, so alg: none or re-signed tokens are rejected.

  • Sessions — each token carries the user's current session_id. Logging in again or refreshing replaces it, which revokes every older token; a refresh token works once.

  • CurrentUser (src/security.rs) — an actix extractor: reads the Bearer header or the token cookie, verifies the JWT, and loads the user from MongoDB. Put it in a handler's arguments to require login, Option<CurrentUser> to make it optional, and call current_user.require(Role::Admin)? for a role check.

  • Passwords — bcrypt (cost 12), hashed on actix's blocking pool so slow hashing never stalls request handling.

  • Roles — sign-up always creates a User. Promote an admin on the database; roles are read from MongoDB on every request, so it applies immediately:

    docker compose exec db mongosh demo --eval \
      'db.users.updateOne({username: "admin"}, {$set: {roles: ["User", "Admin"]}})'
    
  • Errors are always JSON: {"error": "message"}.

Try it:

curl -X POST localhost:8080/api/user/create -H 'content-type: application/json' \
  -d '{"email":"user@test.com","username":"user","password":"supersecret"}'
TOKEN=$(curl -s localhost:8080/api/auth/login -H 'content-type: application/json' \
  -d '{"email":"user@test.com","password":"supersecret"}' | jq -r .tokens.access_token)
curl -H "Authorization: Bearer $TOKEN" localhost:8080/api/protected/user    # 200
curl -H "Authorization: Bearer $TOKEN" localhost:8080/api/protected/admin   # 403

Layout

src/main.rs             env, MongoDB connection, routes
src/security.rs         JWT keys, bcrypt, the CurrentUser extractor
src/auth/service.rs     login, refresh and token → user, with session rotation
src/users/service.rs    sign-up, lookup, unique indexes
src/test_controller.rs  the public / user / admin demo endpoints
src/errors.rs           ApiError → {"error": ...} responses

Deploy

Push to your own GitHub repo and the shipped workflow (.github/workflows/ci.yml) tests the compose stack, publishes the image to GHCR, and — once you set the DEPLOY_HOST / DEPLOY_USER variables and DEPLOY_KEY secret — deploys it to your server over ssh. Set a long random AUTH_SECRET on the server; the one in compose.yaml is for local use only.


Part of devai.io — Rust API boilerplates, alongside rust-crud-sql-api and rust-crud-nosql-api.