Skip to content

Updated Rust backend guide #143

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 34 additions & 109 deletions quickstarts/backend/rust.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,138 +3,63 @@ sidebarTitle: "Rust"
title: Integrate Hanko with Rust backend
---

## Get the Hanko API URL
After successful authentication, Hanko generates a session token that is stored as a cookie. Use the session token to authenticate requests to your backend. This guide demonstrates how to implement session token validation in Rust to ensure that only properly authenticated users can access your application’s protected resources.

### Get the Hanko API URL

Retrieve the API URL from the [Hanko console](https://cloud.hanko.io/).

<Note>If you are self-hosting Hanko you need to provide your own URL.</Note>

## Hanko Authentication with JWT

Upon a successful login, Hanko sends a cookie containing a JSON Web Token ([JWT](https://datatracker.ietf.org/doc/html/rfc7519)). You can use this JWT to authenticate requests on your backend.

### Steps to Authenticate Requests

1. **Recover the [kid](https://datatracker.ietf.org/doc/html/rfc7517#section-4.5) from the user JWT** The kid tells us which key was used to sign the JWT.

2. **Retrieve the JSON Web Key Set ([JWKS](https://datatracker.ietf.org/doc/html/rfc7517)):** The JWKS has the public keys to verify the JWT. Fetch it from the Hanko API's `.well-known/jwks.json` endpoint.

3. **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.

4. **Verify the JWT:** Use the matching JWK to verify the JWT.

<Warning>
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).
</Warning>
1. Retrieve the Session Token.

### Rust-based Backend Example
2. Verify the Session token using the Hanko [Validate](/api-reference/public/session-management/validate-a-session-1) API endpoint.

Below is a sample code in rust validating the user JWT using [reqwest](https://github.com/seanmonstar/reqwest) and [jsonwebtoken](https://github.com/Keats/jsonwebtoken) packages:
### Example function
The following section demonstrates how to validate session tokens against the Hanko backend. The specific implementation for retrieving the session token cookie will vary depending on your framework.

```toml Cargo.toml
[dependencies]
jsonwebtoken = "9.3.0"
reqwest = { version = "0.12.4", features = ["json"]}
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
```

```rust main.rs
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use reqwest;
```rust
use reqwest::Client;
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,
// Configuration constants
struct Config {
hanko_api_url: 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)
impl Config {
fn new() -> Self {
Self {
hanko_api_url: std::env::var("HANKO_API_URL")
.unwrap_or_else(|_| "YOUR_HANKO_API_URL".to_string()),
}
}
}

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())
// Types
#[derive(Serialize)]
struct ValidationPayload<'a> {
session_token: &'a str,
}

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)
#[derive(Deserialize)]
struct ValidationResponse {
is_valid: bool,
}

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);
async fn validate_token(token: &str, client: &Client, config: &Config) -> Result<bool, reqwest::Error> {
let payload = ValidationPayload { session_token: token };
let url = format!("{}/sessions/validate", config.hanko_api_url);

// 4) Verify the JWT.
match matching_jwk {
Some(jwk) => {
let token_data = decode_token(jwk, token)?;
let resp = client.post(&url).json(&payload).send().await?;

// More validations related to the decoded token...

Ok(token_data)
}
None => Err(format!("No matching JWK found matching token kid: {}", jwt_kid).into()),
if !resp.status().is_success() {
return Ok(false);
}
}

#[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),
}
let validation_data: ValidationResponse = resp.json().await?;
Ok(validation_data.is_valid)
}

```