How to
Use Plugins for Access Control
Learn how to use plugins to add access control features like IP restrictions and usage limits.
Plugin Basics
Plugins extend UsefulKey with additional features. They can:
- Block or allow verification based on custom rules
- Add new methods to your UsefulKey instance
- Track analytics events
- Modify key behavior
IP Access Control
Control which IP addresses can use your API keys.
Static IP Allow List
Create a fixed list of allowed IP addresses:
import { usefulkey, MemoryKeyStore, ipAccessControlStatic } from "usefulkey";
const uk = usefulkey({
adapters: {
keyStore: new MemoryKeyStore(),
},
}, {
plugins: [
ipAccessControlStatic({
allow: ["192.168.1.100", "10.0.0.50"]
})
]
});
Dynamic IP Control (Memory)
Allow runtime changes to IP lists:
import { ipAccessControlMemory } from "usefulkey";
const uk = usefulkey({
adapters: {
keyStore: new MemoryKeyStore(),
},
}, {
plugins: [
ipAccessControlMemory({
allow: ["192.168.1.100"]
})
]
});
// Add more IPs at runtime
uk.ipAccessControl.addAllow("10.0.0.50");
uk.ipAccessControl.removeAllow("192.168.1.100");
// Check current lists
console.log("Allowed IPs:", uk.ipAccessControl.getAllow());
console.log("Denied IPs:", uk.ipAccessControl.getDeny());
Persistent IP Control (Database)
Store IP rules in your database:
import { PostgresKeyStore, ipAccessControlKeystore } from "usefulkey";
const uk = usefulkey({
adapters: {
keyStore: new PostgresKeyStore(pool),
},
}, {
plugins: [
ipAccessControlKeystore({
allow: ["203.0.113.1"]
})
]
});
// Changes persist in database
await uk.ipAccessControlStore.addAllow("203.0.113.2");
await uk.ipAccessControlStore.addDeny("198.51.100.1");
Usage Limits Per Key
Limit how many times each key can be used.
import { usageLimitsPerKey } from "usefulkey";
const uk = usefulkey({
adapters: {
keyStore: new MemoryKeyStore(),
},
}, {
plugins: [
usageLimitsPerKey()
]
});
// Create a key with 100 uses
const { result: keyData } = await uk.createKey({
usesRemaining: 100
});
// Use the key multiple times
for (let i = 0; i < 105; i++) {
const { result: verification } = await uk.verifyKey({
key: keyData.key
});
if (verification.valid) {
console.log(`Use ${i + 1}: ✓ Allowed`);
} else {
console.log(`Use ${i + 1}: ✗ ${verification.reason}`);
}
}
// Check remaining uses
const remaining = await uk.getUsesRemaining(keyData.id);
console.log(`Uses remaining: ${remaining}`);
// Add more uses
await uk.topUpUses(keyData.id, 50);
console.log(`After top-up: ${await uk.getUsesRemaining(keyData.id)}`);
Permissions and Scopes
Control what each key can access using scopes.
import { permissionsScopes } from "usefulkey";
const uk = usefulkey({
adapters: {
keyStore: new MemoryKeyStore(),
},
}, {
plugins: [
permissionsScopes()
]
});
// Create keys with different permissions
const { result: adminKey } = await uk.createKey();
const { result: userKey } = await uk.createKey();
// Give admin key all permissions
await uk.grantScopes(adminKey.id, ["read", "write", "delete"]);
// Give user key limited permissions
await uk.grantScopes(userKey.id, ["read", "write"]);
// Verify with scope requirements
const { result: adminCheck } = await uk.verifyKey({
key: adminKey.key,
scopes: ["read", "delete"] // Requires both scopes
});
const { result: userCheck } = await uk.verifyKey({
key: userKey.key,
scopes: ["read", "delete"] // User doesn't have "delete"
});
console.log(`Admin access: ${adminCheck.valid}`); // true
console.log(`User access: ${userCheck.valid}`); // false (insufficient_scope)
Enable/Disable Keys
Temporarily disable keys without deleting them.
import { enableDisable } from "usefulkey";
const uk = usefulkey({
adapters: {
keyStore: new MemoryKeyStore(),
},
}, {
plugins: [
enableDisable()
]
});
// Create and disable a key
const { result: keyData } = await uk.createKey();
await uk.disableKey(keyData.id);
const { result: disabledCheck } = await uk.verifyKey({ key: keyData.key });
console.log(`Disabled key valid: ${disabledCheck.valid}`); // false
// Re-enable the key
await uk.enableKey(keyData.id);
const { result: enabledCheck } = await uk.verifyKey({ key: keyData.key });
console.log(`Enabled key valid: ${enabledCheck.valid}`); // true
Complete Access Control Example
import {
usefulkey,
MemoryKeyStore,
ipAccessControlMemory,
usageLimitsPerKey,
permissionsScopes,
enableDisable
} from "usefulkey";
async function accessControlExample() {
const uk = usefulkey({
adapters: {
keyStore: new MemoryKeyStore(),
},
}, {
plugins: [
ipAccessControlMemory({ allow: ["192.168.1.100"] }),
usageLimitsPerKey(),
permissionsScopes(),
enableDisable()
]
});
// Create a premium user key
const { result: premiumKey } = await uk.createKey({
usesRemaining: 1000,
metadata: { plan: "premium" }
});
// Set up permissions
await uk.grantScopes(premiumKey.id, ["read", "write", "premium"]);
console.log("=== Testing Premium Key ===");
// Test from allowed IP
const { result: ipAllowed } = await uk.verifyKey({
key: premiumKey.key,
ip: "192.168.1.100",
scopes: ["read", "premium"]
});
console.log(`IP allowed: ${ipAllowed.valid}`);
// Test from blocked IP
const { result: ipBlocked } = await uk.verifyKey({
key: premiumKey.key,
ip: "10.0.0.1",
scopes: ["read"]
});
console.log(`IP blocked: ${ipBlocked.valid} (${ipBlocked.reason})`);
// Test insufficient scopes
const { result: scopeFail } = await uk.verifyKey({
key: premiumKey.key,
ip: "192.168.1.100",
scopes: ["admin"] // Key doesn't have admin scope
});
console.log(`Scope fail: ${scopeFail.valid} (${scopeFail.reason})`);
// Disable the key
await uk.disableKey(premiumKey.id);
const { result: disabled } = await uk.verifyKey({
key: premiumKey.key,
ip: "192.168.1.100"
});
console.log(`Disabled: ${disabled.valid} (${disabled.reason})`);
}
accessControlExample();
Plugin Combinations
You can combine multiple plugins for complex access control:
const uk = usefulkey({
adapters: {
keyStore: new MemoryKeyStore(),
rateLimitStore: new MemoryRateLimitStore(),
},
}, {
plugins: [
// Rate limiting
ratelimit({ default: { kind: "fixed", limit: 100, duration: "1m" } }),
// IP control
ipAccessControlMemory({ allow: ["192.168.1.0/24"] }),
// Usage limits
usageLimitsPerKey(),
// Permissions
permissionsScopes(),
// Enable/disable
enableDisable()
]
});
Each plugin adds its own layer of control, and all must pass for verification to succeed.