AtAuth
1//! Basic token verification example
2//!
3//! This example demonstrates how to verify tokens from an AT Protocol auth gateway.
4//!
5//! Run with: cargo run --example basic_verification
6
7use atauth::{AuthError, TokenPayload, TokenVerifier};
8use std::collections::HashMap;
9
10fn main() {
11 // Your HMAC secret (shared with the auth gateway)
12 // Must be at least 32 bytes (256 bits) for security
13 let secret = b"your-super-secret-key-at-least-32-bytes-long!";
14
15 // Create a verifier
16 let verifier = TokenVerifier::new(secret)
17 .expect("Secret must be at least 32 bytes")
18 .with_clock_skew(60) // Allow 60 seconds clock skew
19 .with_format_validation(true); // Validate DID/handle formats
20
21 // Create a test token (in real use, this comes from the client)
22 let test_payload = create_test_payload();
23 let token = verifier.sign(&test_payload).expect("Failed to sign");
24
25 println!("Generated token: {}", token);
26 println!();
27
28 // Verify the token
29 match verifier.verify(&token) {
30 Ok(payload) => {
31 println!("[OK] Token verified successfully!");
32 println!();
33 println!("User Information:");
34 println!(" DID: {}", payload.did);
35 println!(" Handle: {}", payload.handle);
36 println!(" User ID: {:?}", payload.user_id);
37 println!(" App ID: {:?}", payload.app_id);
38 println!();
39 println!("Token Timing:");
40 println!(" Issued: {} seconds ago", payload.age_seconds());
41 println!(" Expires in: {} seconds", payload.remaining_seconds());
42 println!(" Is expired: {}", payload.is_expired());
43 }
44 Err(e) => {
45 println!("[FAIL] Token verification failed!");
46 println!(" Error: {}", e);
47 println!(" HTTP Status: {}", e.http_status_code());
48 }
49 }
50
51 println!();
52 println!("--- Testing Error Cases ---");
53 println!();
54
55 // Test invalid signature
56 let wrong_verifier = TokenVerifier::new(b"wrong-secret-that-is-32-bytes-long!")
57 .expect("Secret must be at least 32 bytes");
58 match wrong_verifier.verify(&token) {
59 Ok(_) => println!("[FAIL] Should have failed!"),
60 Err(AuthError::InvalidSignature) => {
61 println!("[OK] Correctly rejected invalid signature");
62 }
63 Err(e) => println!("[WARN] Unexpected error: {}", e),
64 }
65
66 // Test invalid format
67 match verifier.verify("not-a-valid-token") {
68 Ok(_) => println!("[FAIL] Should have failed!"),
69 Err(AuthError::InvalidFormat(_)) => {
70 println!("[OK] Correctly rejected invalid format");
71 }
72 Err(e) => println!("[WARN] Unexpected error: {}", e),
73 }
74
75 // Test expired token
76 let expired_payload = create_expired_payload();
77 let expired_token = verifier.sign(&expired_payload).expect("Failed to sign");
78 match verifier.verify(&expired_token) {
79 Ok(_) => println!("[FAIL] Should have failed!"),
80 Err(AuthError::Expired) => {
81 println!("[OK] Correctly rejected expired token");
82 }
83 Err(e) => println!("[WARN] Unexpected error: {}", e),
84 }
85}
86
87fn create_test_payload() -> TokenPayload {
88 let now = chrono::Utc::now().timestamp();
89 TokenPayload {
90 did: "did:plc:z72i7hdynmk6r22z27h6tvur".to_string(),
91 handle: "alice.bsky.social".to_string(),
92 user_id: Some(42),
93 app_id: Some("example-app".to_string()),
94 iat: now,
95 exp: now + 3600, // 1 hour from now
96 nonce: "random-nonce-12345".to_string(),
97 extra: HashMap::new(),
98 }
99}
100
101fn create_expired_payload() -> TokenPayload {
102 let now = chrono::Utc::now().timestamp();
103 TokenPayload {
104 did: "did:plc:expired".to_string(),
105 handle: "expired.bsky.social".to_string(),
106 user_id: None,
107 app_id: None,
108 iat: now - 7200, // 2 hours ago
109 exp: now - 3600, // 1 hour ago (expired)
110 nonce: "expired-nonce".to_string(),
111 extra: HashMap::new(),
112 }
113}