Frontend Decryption
The @djangocfg/crypto package provides client-side decryption for Django-CFG encrypted API responses.
NPM Package: @djangocfg/crypto
Installation
pnpm add @djangocfg/cryptoFeatures
- Web Crypto API - Native browser encryption, zero dependencies
- TypeScript - Full type coverage
- React Hooks - Declarative decryption patterns
- Tree-Shakeable - Only imports what you use
Basic Usage
Create Decryption Client
import { createDecryptionClient } from '@djangocfg/crypto';
const crypto = await createDecryptionClient({
secretKey: process.env.NEXT_PUBLIC_DECRYPT_KEY!,
userId: user.id, // Optional, for per-user keys
iterations: 100_000, // Must match backend (default: 100,000)
});Decrypt API Response
const response = await fetch('/api/products/?encrypt=true');
const encrypted = await response.json();
// Recursively decrypts all encrypted fields
const products = await crypto.decryptObject(encrypted);
console.log(products[0].price); // Decrypted valueDecrypt Single Field
if (crypto.isEncryptedField(product.price)) {
const price = await crypto.decryptField<number>(product.price);
console.log(price); // 99.99
}React Hooks
useDecrypt
Automatically decrypt data on mount:
import { useDecrypt } from '@djangocfg/crypto/react';
function ProductCard({ product }: { product: EncryptedProduct }) {
const { data, isLoading, error } = useDecrypt(product, {
secretKey: process.env.NEXT_PUBLIC_DECRYPT_KEY!,
userId: user.id,
});
if (isLoading) return <Skeleton />;
if (error) return <Error message={error.message} />;
return (
<div>
<h2>{data.name}</h2>
<span>${data.price}</span>
</div>
);
}useLazyDecrypt
Decrypt on demand with manual trigger:
import { useLazyDecrypt } from '@djangocfg/crypto/react';
function ProductPrice({ product }: { product: EncryptedProduct }) {
const { decrypt, data, isLoading } = useLazyDecrypt({
secretKey: process.env.NEXT_PUBLIC_DECRYPT_KEY!,
});
return (
<div>
<button onClick={() => decrypt(product)} disabled={isLoading}>
{isLoading ? 'Decrypting...' : 'Show Price'}
</button>
{data && <span>${data.price}</span>}
</div>
);
}useDecryptionClient
Create a memoized client for multiple decryptions:
import { useDecryptionClient } from '@djangocfg/crypto/react';
function ProductList({ products }: { products: EncryptedProduct[] }) {
const { client, isReady, error } = useDecryptionClient({
secretKey: process.env.NEXT_PUBLIC_DECRYPT_KEY!,
userId: user.id,
});
if (!isReady) return <Loading />;
return (
<ul>
{products.map(product => (
<ProductItem key={product.id} product={product} client={client!} />
))}
</ul>
);
}useIsEncrypted
Check if a value is encrypted:
import { useIsEncrypted } from '@djangocfg/crypto/react';
function PriceDisplay({ value }: { value: unknown }) {
const isEncrypted = useIsEncrypted(value);
if (isEncrypted) {
return <EncryptedBadge />;
}
return <span>{value}</span>;
}Integration Examples
With TanStack Query
import { useQuery } from '@tanstack/react-query';
import { useDecryptionClient } from '@djangocfg/crypto/react';
function useProducts() {
const { client } = useDecryptionClient({
secretKey: process.env.NEXT_PUBLIC_DECRYPT_KEY!,
});
return useQuery({
queryKey: ['products'],
queryFn: async () => {
const res = await fetch('/api/products/?encrypt=true');
const encrypted = await res.json();
return client?.decryptObject(encrypted);
},
enabled: !!client,
});
}With SWR
import useSWR from 'swr';
import { createDecryptionClient } from '@djangocfg/crypto';
// Create client once at module level
let cryptoClient: Awaited<ReturnType<typeof createDecryptionClient>> | null = null;
async function getClient() {
if (!cryptoClient) {
cryptoClient = await createDecryptionClient({
secretKey: process.env.NEXT_PUBLIC_DECRYPT_KEY!,
});
}
return cryptoClient;
}
const fetcher = async (url: string) => {
const client = await getClient();
const res = await fetch(url);
const encrypted = await res.json();
return client.decryptObject(encrypted);
};
function ProductList() {
const { data } = useSWR('/api/products/?encrypt=true', fetcher);
// ...
}TypeScript Types
// Encrypted field envelope
interface EncryptedField {
encrypted: true;
field?: string;
algorithm: 'AES-256-GCM' | 'AES-256-CBC';
iv: string; // base64
data: string; // base64
auth_tag: string; // base64
}
// Configuration
interface DecryptionConfig {
secretKey: string;
userId?: string | number;
sessionId?: string;
iterations?: number; // default: 100,000
keyPrefix?: string; // default: "djangocfg_encryption"
}
// Result type for safe decryption
type DecryptionResult<T> =
| { success: true; data: T }
| { success: false; message: string; code: DecryptionErrorCode };
type DecryptionErrorCode =
| 'INVALID_FORMAT'
| 'KEY_ERROR'
| 'AUTH_FAILED'
| 'DECRYPTION_FAILED';API Reference
Core Functions
| Function | Description |
|---|---|
createDecryptionClient(config) | Create client with cached key |
decryptField<T>(field, key) | Decrypt single field |
decryptObject<T>(data, key) | Recursively decrypt object |
isEncryptedField(value) | Type guard for encrypted fields |
safeDecrypt<T>(fn) | Error-handled decryption |
Key Derivation
| Function | Description |
|---|---|
deriveKeyFromConfig(config) | Derive key from config (PBKDF2) |
deriveKey(password, salt, iterations) | Derive key from password + salt |
buildSalt(keyPrefix, userId, sessionId) | Generate salt from components |
Error Handling
Use safeDecrypt for error handling:
import { safeDecrypt, createDecryptionClient } from '@djangocfg/crypto';
const crypto = await createDecryptionClient({ secretKey: '...' });
const result = await safeDecrypt(() => crypto.decryptObject(data));
if (result.success) {
console.log(result.data);
} else {
console.error(result.code, result.message);
// Handle: 'INVALID_FORMAT' | 'KEY_ERROR' | 'AUTH_FAILED' | 'DECRYPTION_FAILED'
}Browser Compatibility
| Browser | Version |
|---|---|
| Chrome | 37+ |
| Firefox | 34+ |
| Safari | 11+ |
| Edge | 12+ |
| Node.js | 15+ (with Web Crypto) |
Security Notes
Important Security Considerations
- Environment Variables - Store
secretKeyin env variables, never hardcode - Per-User Keys - Use
userIdfor user isolation - HTTPS Required - Key derivation happens in browser memory
- Iterations Match -
iterationsmust match backend (default: 100,000)
See Also
- Encryption Overview - Backend configuration
- Configuration Guide - DjangoConfig setup
Last updated on