github_app_forge/jwt.rs
1//! JWT generation for GitHub App authentication.
2//!
3//! After app creation we have the App ID + private key (PEM). For any API
4//! call that operates on the App's identity (e.g. listing installations,
5//! issuing installation access tokens), we sign a short-lived JWT with the
6//! private key as RS256. GitHub then trades that JWT for an installation
7//! access token scoped to a specific installation.
8
9use anyhow::{Context, Result};
10use chrono::Utc;
11use jsonwebtoken::{encode, EncodingKey, Header};
12use serde::Serialize;
13
14#[derive(Debug, Serialize)]
15struct Claims {
16 iat: i64,
17 exp: i64,
18 iss: String,
19}
20
21/// Sign a JWT for the given app id + PEM private key. Lives 9 minutes
22/// (GitHub's max is 10; subtract 1 for clock skew).
23pub fn sign_app_jwt(app_id: u64, private_key_pem: &str) -> Result<String> {
24 let now = Utc::now().timestamp();
25 let claims = Claims {
26 iat: now - 60, // 1 min back-dated for clock skew
27 exp: now + 9 * 60,
28 iss: app_id.to_string(),
29 };
30 let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes())
31 .context("failed to parse RSA private key from PEM")?;
32 encode(&Header::new(jsonwebtoken::Algorithm::RS256), &claims, &key)
33 .context("failed to sign JWT")
34}