Something went wrong. Try again.
AtAuth
Something went wrong. Try again.
Files
- TypeScript 57%
- Rust 43%
README.md
ATAuth - AT Protocol Authentication Library #
A complete, plug-and-play authentication system for AT Protocol (Bluesky) OAuth integration.
Components #
| Component | Description |
|---|---|
| gateway/ | Node.js OAuth gateway server |
| src/ | Rust token verification library |
| ts/ | TypeScript/React frontend utilities |
Features #
- OAuth Gateway: Ready-to-deploy AT Protocol OAuth server
- Token Verification: Secure HMAC-SHA256 with constant-time comparison
- Session Management: Trait-based storage with SQLite and PostgreSQL backends
- Rate Limiting: IP-based rate limiting with configurable thresholds
- Input Validation: DID and handle format validation
- TypeScript Support: Full TypeScript/React utilities for frontend integration
Architecture #
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Your Frontend │────▶│ ATAuth Gateway │────▶│ Bluesky PDS │
│ (React/Next) │ │ (Node.js) │ │ (OAuth Provider)│
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │
│ token │ HMAC secret
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Your Backend │────▶│ Token Verify │
│ (Rust/Node) │ │ (atauth lib) │
└─────────────────┘ └─────────────────┘
Installation #
Rust #
Add to your Cargo.toml:
[dependencies]
atauth = { git = "https://github.com/Cache8063/atauth" }
Or with specific features:
[dependencies]
atauth = { git = "https://github.com/Cache8063/atauth", features = ["session-sqlite", "rate-limit"] }
TypeScript/JavaScript #
npm install atauth
# or
pnpm add atauth
Quick Start #
Rust - Basic Token Verification #
use atauth::{TokenVerifier, TokenPayload};
// Create a verifier with your HMAC secret (shared with auth gateway)
let verifier = TokenVerifier::new(b"your-secret-key");
// Verify a token from the auth gateway
match verifier.verify("token-from-client") {
Ok(payload) => {
println!("Authenticated: {} ({})", payload.handle, payload.did);
// payload.user_id contains app-specific user ID if linked
}
Err(e) => {
eprintln!("Auth failed: {}", e);
}
}
Rust - With Session Store #
use atauth::{TokenVerifier, SessionManager};
use atauth::session::SqliteSessionStore;
// Setup
let verifier = TokenVerifier::new(b"your-secret-key");
let session_store = SqliteSessionStore::new("sessions.db")?;
let sessions = SessionManager::new(session_store);
// On login
if let Ok(payload) = verifier.verify(token) {
let session = sessions.create_session(&payload, token.to_string())?;
// Return session.token to client as session cookie
}
// On subsequent requests
match sessions.validate(session_token) {
Ok(session) => {
// User is authenticated
println!("User: {}", session.handle);
}
Err(_) => {
// Invalid or expired session
}
}
// On logout
sessions.invalidate(session_token)?;
Rust - With Rate Limiting #
use atauth::rate_limit::{RateLimiter, RateLimiterConfig};
use std::time::Duration;
let config = RateLimiterConfig::default()
.with_max_attempts(5)
.with_lockout(Duration::from_secs(300));
let limiter = RateLimiter::new(config);
// Before authentication attempt
let client_ip = "192.168.1.1".parse().unwrap();
if let Err(e) = limiter.check(&client_ip) {
return Err(e); // Rate limited
}
// After failed attempt
limiter.record_failure(&client_ip);
// After successful attempt
limiter.record_success(&client_ip);
TypeScript - React Integration #
import { initAuthStore, useAuthStore } from 'atauth/react';
// Initialize once at app startup
initAuthStore({
gatewayUrl: 'https://auth.example.com',
appId: 'myapp',
callbackUrl: 'https://myapp.com/auth/callback',
});
function LoginButton() {
const { isAuthenticated, user, login, logout, isLoading } = useAuthStore();
if (isLoading) {
return <div>Loading...</div>;
}
if (isAuthenticated) {
return (
<div>
<span>Welcome, {user?.handle}!</span>
<button onClick={logout}>Logout</button>
</div>
);
}
return <button onClick={() => login()}>Login with Bluesky</button>;
}
TypeScript - Manual Token Handling #
import {
decodeToken,
handleCallback,
isOAuthCallback,
redirectToAuth
} from 'atauth';
// Redirect to auth
redirectToAuth({
gatewayUrl: 'https://auth.example.com',
appId: 'myapp',
callbackUrl: window.location.origin + '/callback',
});
// Handle callback page
if (isOAuthCallback()) {
const result = handleCallback({ gatewayUrl: 'https://auth.example.com' });
if (result.success) {
console.log('Logged in as:', result.user?.handle);
// Redirect to app
window.location.href = result.returnTo || '/';
} else {
console.error('Auth failed:', result.error);
}
}
Token Format #
Tokens follow a JWT-like structure: base64url(payload).base64url(signature)
Payload Structure #
{
"did": "did:plc:abc123...",
"handle": "user.bsky.social",
"user_id": 42,
"app_id": "myapp",
"iat": 1699900000,
"exp": 1699903600,
"nonce": "random-string"
}
API Reference #
Rust #
TokenVerifier #
new(secret: &[u8])- Create verifier with HMAC secretfrom_hex(hex: &str)- Create from hex-encoded secretfrom_base64(b64: &str)- Create from base64-encoded secretverify(token: &str) -> Result<TokenPayload>- Verify and decode tokensign(payload: &TokenPayload) -> Result<String>- Sign a payload (for gateways)
SessionManager<S: SessionStore> #
new(store: S)- Create manager with storecreate_session(payload, token)- Create session from tokenvalidate(token)- Validate and optionally extend sessioninvalidate(token)- Delete sessioninvalidate_all_for_did(did)- Delete all sessions for usercleanup()- Remove expired sessions
RateLimiter #
new(config)- Create with configurationcheck(ip)- Check if IP can attempt authrecord_failure(ip)- Record failed attemptrecord_success(ip)- Clear on success
TypeScript #
Token Utilities #
decodeToken(token)- Decode without verificationisTokenExpired(payload)- Check expirationgetTokenRemainingSeconds(payload)- Time until expiryisValidDid(did)- Validate DID formatisValidHandle(handle)- Validate handle format
OAuth Utilities #
redirectToAuth(config, state?)- Start OAuth flowhandleCallback(config)- Process callbackisOAuthCallback()- Check if on callback pagebuildLogoutUrl(config)- Get logout URL
React Hooks #
useAuthStore()- Main auth hookuseUser()- Get current useruseIsAuthenticated()- Check auth statususeNeedsRefresh()- Check if token needs refresh
Security Considerations #
- Server-side verification: Always verify tokens server-side. Client-side decoding is for display only.
- Constant-time comparison: Signature verification uses constant-time comparison to prevent timing attacks.
- Rate limiting: Enable rate limiting in production to prevent brute force attacks.
- HTTPS only: Always use HTTPS in production.
- Secure secrets: Store HMAC secrets securely, never in client code.
License #
MIT