This project contains a Rust server that serves a single page application and has authentication + JWT-based authorization.
It was written as a learning exercise and can hopefully be a useful example for a Rust-backed website that uses authentication + authorization. It's a bit more complete and closer to prodution-ready than other example code I've seen online, e.g. JWT with warp.
Though I am somewhat informed, I am not a security expert. Don't deploy this code to production.
2021-04-03.19-16-28.mp4
- A recent version of Rust+Cargo (MSRV unknown)
- A recent version of npm (minimum unknown)
If you check Cargo.toml, you'll see that the warp
dependency is my personal warp fork. This is due to waiting on my
PR for more convenient rejection
handling to be merged.
- Rust with a Warp web server
- Authentication using Argon2 password hashing to produce refresh token cookies
- Authorization with 2 basic roles using JWT access tokens for claims
- Optional CORS for more rapid client side development
- Example for abstracting a data store with a trait
- In-memory implementation exists
- Vue 2.X framework
- Axios for API requests
- Login
- Logout
- Conditionally visible UI components based on JWT claims
- Automatic refreshing of access tokens on 403 error
I am not the most proficient client-side dev, so the structure of the client side code may not be what you want to emulate. The API requests using axios are probably the most useful to look at with regards to using the server APIs.
Most of the code is hopefully not closely tied to Warp framework details — most
of the Warp-specific code is in routes.rs
with a sprinkle in main.rs
and
error.rs
. As long as the server framework used is async capable, the rest of
it should be a decent starting point for use with other server frameworks.
Since the webserver uses Warp, the code uses the tokio runtime. Apart from
the Warp related code, the auth
module has a few instances where it is
reliant on tokio. These are pretty minimal so it should be simple to adapt for
webservers with another runtime, e.g. Tide.
Instances of tokio reliance:
init_default_users
: usesblock_on
to run async code in a sync functionstore_user
: spawns a blocking task to run password hashingauthenticate
: spawns a blocking task to run password verificationpretend_password_processing
: uses tokio sleep#[tokio::test]
is used for async tests
The command sequence below uses an in-memory data store. To use sqlite, different commands are needed.
cd $(git rev-parse --show-toplevel)
./build-debug.sh
cd build-output
./rust-spa-auth
To serve the SPA and the server separately for more rapid client side code development, you can use the following commands:
Note - you may have to navigate to https://localhost:9090 manually and accept the certificate warning before this works.
Serve client files:
cd $(git rev-parse --show-toplevel)/client
npm run serve
Run server:
cd $(git rev-parse --show-toplevel)/server
cargo run --features in_memory,dev_cors
# Can omit `in_memory` from above, but a database will need to be specified
# e.g.
export DATABASE_URL=sqlite:///tmp/db.sql
cargo run --features dev_cors -- --database $DATABASE_URL
You can check the API functionality without your browser using cURL.
See an example sequence below.
curl -v https://localhost:9090/api/login \
--cacert tls/server.rsa.crt \
-d '{"email": "user@localhost", "pw": "userpassword"}' \
-H 'Content-Type: application/json'
# result is in set-cookie header:
# set-cookie: refresh_token=QpOddMUkW9wk/S4B.s/a3k3JttPFH3v4j43gxx7KL+3y05Opm1rjiQBV+07z9NXacLv8PeQn6DRDoblFDerGQ9qeUp1TpaNAg5f1cYtLf3t3xnvGkHUDW2TK/mDJr4A=="; Max-Age=2592000; path=/api/auth/access; Secure; HttpOnly; SameSite=Lax;
curl https://localhost:9090/api/auth/access \
--cacert tls/server.rsa.crt \
--cookie "refresh_token=QpOddMUkW9wk/S4B.s/a3k3JttPFH3v4j43gxx7KL+3y05Opm1rjiQBV+07z9NXacLv8PeQn6DRDoblFDerGQ9qeUp1TpaNAg5f1cYtLf3t3xnvGkHUDW2TK/mDJr4A=="
# result:
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJlbWFpbCI6InVzZXJAbG9jYWxob3N0Iiwicm9sZSI6InVzZXIiLCJleHAiOjE2MTY5MjY2NTd9.kj9GR-FPUVmZh2BEvGmbqg6tAz4lsjvLxtcTXOjdDXLwD0KGZ2NrDueuuyJ1Y4z8z98q9VcpDNHYjS4veM2hYw
curl https://localhost:9090/api/user \
--cacert tls/server.rsa.crt \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJlbWFpbCI6InVzZXJAbG9jYWxob3N0Iiwicm9sZSI6InVzZXIiLCJleHAiOjE2MTcwNjUxMDJ9.imixaRk8YgoEv8Hh33qidty_jGBAo9ewIOd7vWqAjAHiN-MZJOFeSXg25nWx86SW9Pc_QFH_qlFYaSmPG_MfRA'
# result:
# user user@localhost
curl https://localhost:9090/api/admin \
--cacert tls/server.rsa.crt \
-H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJlbWFpbCI6InVzZXJAbG9jYWxob3N0Iiwicm9sZSI6InVzZXIiLCJleHAiOjE2MTcwNjUxMDJ9.imixaRk8YgoEv8Hh33qidty_jGBAo9ewIOd7vWqAjAHiN-MZJOFeSXg25nWx86SW9Pc_QFH_qlFYaSmPG_MfRA'
# result:
# {"message":"no permission","status":"403 Forbidden"}⏎
curl https://localhost:9090/api/auth/logout \
-X POST \
--cacert tls/server.rsa.crt \
--cookie "refresh_token=QpOddMUkW9wk/S4B.s/a3k3JttPFH3v4j43gxx7KL+3y05Opm1rjiQBV+07z9NXacLv8PeQn6DRDoblFDerGQ9qeUp1TpaNAg5f1cYtLf3t3xnvGkHUDW2TK/mDJr4A=="
- auth rate limit
- http to https redirect
- delete the cookie on the client on logout
- not really necessary, but can do for cleanliness
- lets-encrypt certificates
- add APIs for add/delete user
- casbin
These sources were useful starting points.
This project is licensed under the MIT license.
Pull requests are welcome. The goal of this project is to serve as a useful example for building a website with a Rust backend that includes some security.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion by you shall be licensed as MIT without any additional terms or conditions.