Authentication
Ideally we don't want to implement Authentication either ourselves or through a library. I've done this many times and now what I do is assume all calls to the server are pre-authenticated and contain a Bearer in the header with a JWT.
To do this in production we can use Oauth2 Proxy and attach it to our preferred identity provider. This will supply us with a JWT identifing the user who is making the calls.
In development we can override this with an env var.
use core::str; use axum::{ extract::FromRequestParts, http::{request::Parts, StatusCode}, }; use base64::{decode_config, URL_SAFE_NO_PAD}; use db::authz; use serde::{Deserialize, Serialize}; use serde_json::Value; const X_FORWARDED_ACCESS_TOKEN: &str = "X-Forwarded-Access-Token"; const X_FORWARDED_USER: &str = "X-Forwarded-User"; const X_FORWARDED_EMAIL: &str = "X-Forwarded-Email"; const DANGER_JWT_OVERRIDE: &str = "DANGER_JWT_OVERRIDE"; #[derive(Serialize, Deserialize, Debug)] pub struct Jwt { pub sub: String, pub email: String, pub given_name: Option<String>, pub family_name: Option<String>, } // Convert it to something the db crate understands impl From<Jwt> for authz::Authentication { fn from(val: Jwt) -> Self { authz::Authentication { sub: val.sub, email: val.email, given_name: val.given_name, family_name: val.family_name, } } } impl<S> FromRequestParts<S> for Jwt where S: Send + Sync, { type Rejection = (StatusCode, &'static str); async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> { let access_token = if let Ok(override_token) = std::env::var(DANGER_JWT_OVERRIDE) { Some(override_token) } else { // Fall back to header, convert &str to String parts .headers .get(X_FORWARDED_ACCESS_TOKEN) .and_then(|header| header.to_str().ok()) .map(|s| s.to_string()) }; let forwarded_user = parts.headers.get(X_FORWARDED_USER); let forwarded_email = parts.headers.get(X_FORWARDED_EMAIL); if let Some(access_token) = access_token { let jwt_parts: Vec<&str> = access_token.split('.').collect(); if jwt_parts.len() == 3 { if let Ok(payload) = decode_config(jwt_parts[1], URL_SAFE_NO_PAD) { if let Ok(payload_str) = str::from_utf8(&payload) { let json_value: Result<Value, _> = serde_json::from_str(payload_str); if let Ok(json_value) = json_value { if let Some(sub) = json_value.get("sub").and_then(|v| v.as_str()) { if let Some(email) = json_value.get("email").and_then(|v| v.as_str()) { let given_name = json_value .get("given_name") .and_then(|v| v.as_str()) .map(String::from); let family_name = json_value .get("family_name") .and_then(|v| v.as_str()) .map(String::from); let authentication = Jwt { sub: sub.to_string(), email: email.to_string(), given_name, family_name, }; return Ok(authentication); } } } } } } } else if let (Some(user), Some(email)) = (forwarded_user, forwarded_email) { let user = user.to_str(); let email = email.to_str(); if let (Ok(sub), Ok(email)) = (user, email) { let authentication = Jwt { sub: sub.to_string(), email: email.to_string(), given_name: None, family_name: None, }; return Ok(authentication); } } Err(( StatusCode::UNAUTHORIZED, "Didn't find an authentication header", )) } }