Description
This example shows how to use authentication within actix-web with cookies, assisted by actix-identity and actix-session.
The idea is that all requests authenticate first at the login route to get a cookie, then the cookie is sent with all requests requiring authentication using the HTTP cookie header.
You can clone the example below by running the following (you’ll need cargo-shuttle
installed):
cargo shuttle init --from shuttle-hq/shuttle-examples \
--subfolder actix-web/cookie-authentication
Three Actix Web routes are registered in this file:
/public
: a route that can be called without needing any authentication.
/login
: a route for posting a JSON object with a username and password to get a cookie.
/private
: a route that will display whether you’re logged in or not, based on if you’re logged in.
The example uses actix-identity
and actix-session
with a cookie store to assist with easy setup.
Code
[package]
name = "authentication"
version = "0.1.0"
edition = "2021"
[dependencies]
actix-identity = "0.6.0"
actix-session = { version = "0.8.0", features = ["cookie-session"] }
actix-web = "4.3.1"
shuttle-actix-web = "0.27.0"
shuttle-runtime = "0.27.0"
tokio = "1.26.0"
Your main.rs
should look like this:
use actix_web::{get, web::ServiceConfig};
use shuttle_actix_web::ShuttleActixWeb;
use actix_identity::{Identity, IdentityMiddleware};
use actix_session::{config::PersistentSession, storage::CookieSessionStore, SessionMiddleware};
use actix_web::{
cookie::{time::Duration, Key},
error,
http::StatusCode,
middleware, web, HttpMessage as _, HttpRequest, Responder,
};
const FIVE_MINUTES: Duration = Duration::minutes(5);
#[get("/")]
async fn index(identity: Option<Identity>) -> actix_web::Result<impl Responder> {
let id = match identity.map(|id| id.id()) {
None => "anonymous".to_owned(),
Some(Ok(id)) => id,
Some(Err(err)) => return Err(error::ErrorInternalServerError(err)),
};
Ok(format!("Hello {id}"))
}
#[get("/login")]
async fn login(req: HttpRequest) -> impl Responder {
Identity::login(&req.extensions(), "user1".to_owned()).unwrap();
web::Redirect::to("/").using_status_code(StatusCode::FOUND)
}
#[get("/logout")]
async fn logout(id: Identity) -> impl Responder {
id.logout();
web::Redirect::to("/").using_status_code(StatusCode::FOUND)
}
#[shuttle_runtime::main]
async fn actix_web() -> ShuttleActixWeb<impl FnOnce(&mut ServiceConfig) + Send + Clone + 'static> {
let secret_key = Key::generate();
let config = move |cfg: &mut ServiceConfig| {
cfg.service(
web::scope("/")
.service(index).service(login).service(logout)
.wrap(IdentityMiddleware::default())
.wrap(
SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
.cookie_name("auth-example".to_owned())
.cookie_secure(false)
.session_lifecycle(PersistentSession::default().session_ttl(FIVE_MINUTES))
.build(),
)
.wrap(middleware::NormalizePath::trim())
.wrap(middleware::Logger::default())
);
};
Ok(config.into())
}
Usage
Once you’ve cloned this example, launch it locally by using cargo shuttle run
. Once you’ve verified that it’s up, you’ll now be able to go to http://localhost:8000
and start trying the example out!
First, we should be able to access the public endpoint without any authentication using:
$ curl http://localhost:8000/public
But trying to access the private endpoint will return “Hello anonymous”:
$ curl http://localhost:8000/private
So let’s get a cookie from the login route first:
$ curl http://localhost:8000/login
Accessing the private endpoint with the token will now succeed:
$ curl --header "Authorization: Bearer <token>" http://localhost:8000/private
The token is set to expire in 5 minutes, so wait a while and try to access the private endpoint again. Once the token has expired, a user will need to get a new token from login.
Since tokens usually have a longer than 5 minutes expiration time, we can create a /refresh
endpoint that takes an active token and returns a new token with a refreshed expiration time.
Looking to extend this example? Here’s a couple of ideas to get you started:
- Create a frontend to host the login
- Add a route for registering
- Use a database to check login credentials
If you want to explore other frameworks, we have more examples with popular ones like Tower and Warp. You can find them
right here.