Identity-aware encryption
Tie encryption to a user's JWT so only that user can decrypt their data
Identity-aware encryption
Lock encryption to a specific user by requiring a valid JWT for decryption. When a value is encrypted with a lock context, it can only be decrypted by presenting the same user's identity token.
How it works
Lock contexts require a Business or Enterprise workspace plan.
Lock contexts are useful for:
- Multi-tenant applications where each user's data must be isolated
- Compliance requirements that demand per-user encryption boundaries
- Applications where you need to prove that only authorized users accessed specific records
The flow is:
- Create a
LockContextinstance. - Identify the user with their JWT.
- Pass the lock context to encrypt and decrypt operations.
Basic usage
import { LockContext } from "@cipherstash/stack/identity"
// 1. Create a lock context (defaults to the "sub" claim)
const lc = new LockContext()
// 2. Identify the user with their JWT
const identifyResult = await lc.identify(userJwt)
if (identifyResult.failure) {
throw new Error(identifyResult.failure.message)
}
const lockContext = identifyResult.data
// 3. Encrypt with lock context
const encrypted = await client
.encrypt("sensitive data", { column: users.email, table: users })
.withLockContext(lockContext)
// 4. Decrypt with the same lock context
const decrypted = await client
.decrypt(encrypted.data)
.withLockContext(lockContext)Supported operations
Lock contexts work with all encrypt and decrypt operations:
// Single operations
const encrypted = await client
.encryptModel(user, users)
.withLockContext(lockContext)
const decrypted = await client
.decryptModel(encryptedUser)
.withLockContext(lockContext)
// Bulk operations
const bulkEncrypted = await client
.bulkEncryptModels(userModels, users)
.withLockContext(lockContext)
const bulkDecrypted = await client
.bulkDecryptModels(encryptedUsers)
.withLockContext(lockContext)
// Query operations
const term = await client
.encryptQuery("[email protected]", {
column: users.email,
table: users,
})
.withLockContext(lockContext)Custom identity claims
Override the default context by specifying which identity claims to use:
const lc = new LockContext({
context: {
identityClaim: ["sub"], // this is the default
},
})| Identity claim | Description |
|---|---|
sub | The user's subject identifier |
scopes | The user's scopes set by your IDP policy |
Combine claims for identity and permissions scoping:
const lc = new LockContext({
context: {
identityClaim: ["sub", "scopes"],
},
})Using with Clerk and Next.js
Install the @cipherstash/nextjs package for automatic CTS token setup with Clerk:
npm install @cipherstash/nextjsSet up middleware
In your middleware.ts, use protectClerkMiddleware to automatically generate CTS tokens for every user session:
import { clerkMiddleware } from "@clerk/nextjs/server"
import { protectClerkMiddleware } from "@cipherstash/nextjs/clerk"
export default clerkMiddleware(async (auth, req) => {
return protectClerkMiddleware(auth, req)
})Retrieve the CTS token
Use getCtsToken to get the CTS token for the current user:
import { getCtsToken } from "@cipherstash/nextjs"
export default async function Page() {
const ctsToken = await getCtsToken()
if (!ctsToken.success) {
// handle error
}
// ctsToken is ready to use
}Create a LockContext with an existing CTS token
Since the CTS token is already available from the middleware, construct the LockContext directly. The CTS token has the shape { accessToken: string, expiry: number }. Passing it directly avoids a second round-trip to CTS.
import { LockContext } from "@cipherstash/stack/identity"
import { getCtsToken } from "@cipherstash/nextjs"
export default async function Page() {
const ctsToken = await getCtsToken()
if (!ctsToken.success) {
// handle error
}
const lockContext = new LockContext({ ctsToken })
// Use lockContext with encrypt/decrypt operations
}getCtsToken returns { success: true, ctsToken: CtsToken } on success or { success: false, error: string } on failure.
Error handling
The identify method returns a Result type:
const result = await lc.identify(userJwt)
if (result.failure) {
// result.failure.type is 'CtsTokenError'
console.error("CTS token exchange failed:", result.failure.message)
}Common failure scenarios:
| Scenario | Error type | Description |
|---|---|---|
| Invalid JWT | CtsTokenError | The JWT token was rejected by CTS |
| Network failure | CtsTokenError | Could not reach the CTS endpoint |
| Missing workspace | Runtime error | CS_WORKSPACE_CRN is not configured |
| Expired CTS token | LockContextError | The CTS token has expired. Call identify again |