Integrate Hanko with Rust backend
Get the Hanko API URL
Retrieve the API URL from the Hanko console.
Hanko Authentication with JWT
Upon a successful login, Hanko sends a cookie containing a JSON Web Token (JWT). You can use this JWT to authenticate requests on your backend.
Steps to Authenticate Requests
-
Recover the kid from the user JWT The kid tells us which key was used to sign the JWT.
-
Retrieve the JSON Web Key Set (JWKS): The JWKS has the public keys to verify the JWT. Fetch it from the Hanko API’s
.well-known/jwks.json
endpoint. -
Find the matching JWK from the JWKS retrieved at step 2 The matching JWK is the one that has the same kid found at step 1.
-
Verify the JWT: Use the matching JWK to verify the JWT.
The JWKS should be cached in the backend to avoid querying the endpoint every time a token needs to be validated. It is recommended to retrieve the JWKS when no JWK matches the JWT kid (step 3).
Rust-based Backend Example
Below is a sample code in rust validating the user JWT using reqwest and jsonwebtoken packages:
[dependencies]
jsonwebtoken = "9.3.0"
reqwest = { version = "0.12.4", features = ["json"]}
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use reqwest;
use serde::{Deserialize, Serialize};
use std::error::Error;
const HANKO_API_URL: &str = "https://XXXXX.hanko.io";
const JWT_AUDIENCE_DOMAIN: &str = "example.com";
#[derive(Debug, Deserialize)]
struct Jwk {
kty: String,
kid: String,
n: String,
e: String,
alg: String,
}
#[derive(Debug, Deserialize)]
struct Jwks {
keys: Vec<Jwk>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct EmailInfo {
pub address: String,
pub is_primary: bool,
pub is_verified: bool,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct UserDecodedJwtInfo {
pub aud: Vec<String>,
pub email: EmailInfo,
pub exp: i64,
pub iat: i64,
pub sub: String,
}
async fn fetch_jwks() -> Result<Jwks, Box<dyn Error>> {
let response = reqwest::get(format!("{}/.well-known/jwks.json", HANKO_API_URL)).await?;
let jwks = response.json::<Jwks>().await?;
Ok(jwks)
}
fn get_jwt_kid(token: &str) -> Result<String, Box<dyn Error>> {
let header = decode_header(token)?;
header.kid.ok_or_else(|| "Missing kid in header".into())
}
fn decode_token(jwk: &Jwk, token: &str) -> Result<UserDecodedJwtInfo, Box<dyn Error>> {
// Decode the token to return its information.
let decoding_key = DecodingKey::from_rsa_components(&jwk.n, &jwk.e)?;
let mut validation = Validation::new(Algorithm::RS256);
validation.set_audience(&[JWT_AUDIENCE_DOMAIN.to_string()]);
let token_data = decode::<UserDecodedJwtInfo>(token, &decoding_key, &validation)?;
Ok(token_data.claims)
}
async fn validate_hanko_token(token: &str) -> Result<UserDecodedJwtInfo, Box<dyn Error>> {
// 1) Recover the kid from the user JWT.
let jwt_kid = get_jwt_kid(token)?;
// 2) Retrieve the JSON Web Key Set (JWKS).
let jwks = fetch_jwks().await?;
// 3) Find the matching JWK from the JWKS retrieved at step 2*
let matching_jwk = jwks.keys.iter().find(|jwk| jwk.kid == jwt_kid);
// 4) Verify the JWT.
match matching_jwk {
Some(jwk) => {
let token_data = decode_token(jwk, token)?;
// More validations related to the decoded token...
Ok(token_data)
}
None => Err(format!("No matching JWK found matching token kid: {}", jwt_kid).into()),
}
}
#[tokio::main]
async fn main() {
// Depending on the rust backend framework used (axum, artix-web, rocket...),
// recover the hanko token from the request.
let jwt_hanko_token = "some-hanko-jwt-token".to_string();
match validate_hanko_token(&jwt_hanko_token).await {
Ok(payload) => println!("Token is valid. Payload: {:?}", payload),
Err(err) => eprintln!("Token validation failed: {}", err),
}
}