Custom Backend
Introduction
This guide provides comprehensive instructions for integrating FastAuth into your application using a custom authentication backend. A custom backend integration gives you complete control over the JWT signing and validation process while still benefiting from FastAuth's transaction signing capabilities on the NEAR blockchain.
Custom backend integration is ideal for organizations that need to implement custom authentication logic, integrate with existing authentication systems, or require specific JWT claim structures. Through this integration, your application maintains full ownership of the authentication process while leveraging FastAuth's robust transaction signing infrastructure.
The custom backend approach requires deploying and managing your own JWT verification contract (guard contract) and implementing a custom authentication provider that generates properly formatted JWT tokens for transaction signing.
Requirements
Successfully integrating a custom backend with FastAuth requires three essential components: properly structured JWT tokens with required claims, a deployed guard contract for JWT verification, and registration of your guard in the FastAuth router system.
Specify custom claims
Your custom backend must generate JWT tokens that include specific claims required by the FastAuth system. These claims ensure proper transaction verification and user identification.
Required JWT Claims Structure
interface FastAuthJWTClaims {
// Standard JWT claims
iss: string; // Issuer - your backend domain
sub: string; // Subject - unique user identifier
aud?: string; // Audience (optional)
exp: number; // Expiration timestamp
iat: number; // Issued at timestamp
// FastAuth specific claims
fatxn: number[]; // Transaction payload (binary-encoded)
}
Mandatory Claims
sub
(Subject): A unique identifier for the user. This claim is used by FastAuth to derive the user's account path and must remain consistent for the same user across authentication sessions.
fatxn
(FastAuth Transaction): The transaction payload that will be signed, encoded as a binary array. This claim contains the actual transaction data that needs to be verified against the signing request.
Additional Custom Claims
You can extend the JWT with additional custom claims for your specific use case:
interface ExtendedFastAuthJWTClaims extends FastAuthJWTClaims {
// Your custom claims
role?: string;
permissions?: string[];
organizationId?: string;
}
Customize your guard contract
The guard contract is responsible for verifying JWT tokens generated by your custom backend. FastAuth provides a template contract that you can customize for your specific verification requirements, such as JWT algorithm, claims, and more.
Using the JWT RS256 Guard Template
The JWT RS256 Guard Template is a good starting point for implementing your custom JWT verification logic. It supports only RS256 algorithm, but you can easily extend it or replace it to support other algorithms.
The jwt-rs256-guard example contract provides a foundation for implementing your custom JWT verification logic.
Step 1: Customize the Guard Contract
To add custom claims to the guard contract, you need to extend the CustomClaims
struct.
sub
and fatxn
claims are mandatory and must be present in the JWT token. Removing them will break the guard contract.
#[derive(Serialize, Deserialize)]
pub struct CustomClaims {
/// The subject identifier claim that uniquely identifies the user
pub sub: String,
/// The FastAuth claim that specifies the signed payload
pub fatxn: Vec<u8>,
// Add your custom claims here
pub role: Option<String>,
pub permissions: Option<Vec<String>>,
}
Step 2: Customize Verification Logic
Once you have extended the CustomClaims
struct, you can implement your custom verification logic (if needed) in the verify_custom_claims
function.
fn verify_custom_claims(&self, jwt_payload: Vec<u8>, sign_payload: Vec<u8>) -> (bool, String) {
let claims: CustomClaims = match serde_json::from_slice(&jwt_payload) {
Ok(claims) => claims,
Err(error) => return (false, error.to_string()),
};
// Verify fatxn claim (mandatory)
if claims.fatxn != sign_payload {
return (false, "Transaction payload mismatch".to_string());
}
// Add your custom verification logic here
if let Some(role) = &claims.role {
if !self.is_valid_role(role) {
return (false, "Invalid user role".to_string());
}
}
// Return success with subject claim
(true, claims.sub)
}
Step 3: Deploy the Guard Contract
Once the contract is customized and tested, you can deploy it to the NEAR blockchain.
We encourage you to deploy a testnet guard contract first to test your custom logic. Once you are satisfied with the results, you can deploy the contract to the mainnet.
Register your guard
After deploying your custom guard contract, you must register it with the FastAuth JWT Guard Router to make it available for transaction verification.
Registration Process
Step 1: Prepare Registration Information
- Guard Name: A unique identifier for your guard (without the "jwt#" prefix). Must match your issuer JWT claim.
- Guard Account: The NEAR account ID where your guard contract is deployed.
Step 2: Register Your Guard
near call jwt-guard-router.testnet add_guard '{
"guard_name": "your-custom-backend",
"guard_account": "your-guard.testnet"
}' --accountId your-account.testnet --deposit 1.03
Verification of Registration
Confirm your guard is properly registered:
near view jwt-guard-router.testnet get_guard '{"guard_name": "your-custom-backend"}'
Choose an SDK
FastAuth provides SDKs for integrating custom backend authentication into your applications. Currently, the Browser SDK is the primary option for web-based integrations.
Browser SDK
The FastAuth Browser SDK (@fast-auth/browser
) supports custom backend integration through the implementation of a custom provider that communicates with your authentication backend.
Implement your custom backend provider
To be able to use your custom backend solution with any SDK, you must implement the IFastAuthProvider
interface that handles communication between the SDK and your authentication system.
import { IFastAuthProvider } from "@fast-auth/browser";
import { SignatureRequest } from "@fast-auth/browser";
interface IFastAuthProvider {
// Authentication state management
isLoggedIn(): Promise<boolean>;
login(...args: any[]): void;
logout(): void;
// Transaction signing requests
requestTransactionSignature(...args: any[]): Promise<void>;
requestDelegateActionSignature(...args: any[]): Promise<void>;
// Signature data retrieval
getSignatureRequest(): Promise<SignatureRequest>;
getPath(): Promise<string>;
}