API keys overview
Manage your APIs
When end users make requests to your API using their API keys, you need to verify those keys with Kinde before processing the request.
This guide shows you how to implement API key verification in your API endpoints.
The verification flow follows these steps:
First, extract the API key from the incoming request:
// Express.js examplefunction extractApiKey(req) { const authHeader = req.headers.authorization;
if (!authHeader) { throw new Error('No authorization header'); }
if (!authHeader.startsWith('Bearer ')) { throw new Error('Invalid authorization format'); }
return authHeader.substring(7); // Remove 'Bearer ' prefix}
// FastAPI exampledef extract_api_key(authorization: str = Header(None)): if not authorization: raise HTTPException(status_code=401, detail="No authorization header")
if not authorization.startswith("Bearer "): raise HTTPException(status_code=401, detail="Invalid authorization format")
return authorization[7:] # Remove 'Bearer ' prefix
Call Kinde’s verification endpoint to validate the key:
// Node.js/Express exampleasync function verifyApiKey(apiKey) { try { const response = await fetch('https://your-domain.kinde.com/api/v1/api_keys/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ api_key: apiKey }) });
if (!response.ok) { throw new Error(`Verification failed: ${response.status}`); }
return await response.json(); } catch (error) { console.error('API key verification error:', error); throw new Error('Failed to verify API key'); }}
// Python/FastAPI exampleasync def verify_api_key(api_key: str): async with httpx.AsyncClient() as client: response = await client.post( "https://your-domain.kinde.com/api/v1/api_keys/verify", headers={ "Content-Type": "application/json" }, json={ "api_key": api_key } )
if not response.is_success: raise HTTPException(status_code=400, detail="Verification failed")
return response.json()
Use the verification in your API endpoints:
// Express.js endpointapp.get('/api/data', async (req, res) => { try { // Extract API key const apiKey = extractApiKey(req);
// Verify with Kinde const verification = await verifyApiKey(apiKey);
if (!verification.is_valid) { return res.status(401).json({ error: 'Invalid API key' }); }
// Check if key is active if (verification.status !== 'active') { return res.status(401).json({ error: 'API key is not active' }); }
// Check required scopes if (!verification.scopes.includes('read:users')) { return res.status(403).json({ error: 'Insufficient scope' }); }
// Process request const data = await getData(); res.json({ data, key_id: verification.key_id });
} catch (error) { console.error('API key verification error:', error); res.status(500).json({ error: 'Internal server error' }); }});
# FastAPI endpoint@app.get("/api/data")async def get_data(authorization: str = Header(None)): try: # Extract API key api_key = extract_api_key(authorization)
# Verify with Kinde verification = await verify_api_key(api_key)
if not verification["is_valid"]: raise HTTPException(status_code=401, detail="Invalid API key")
# Check if key is active if verification["status"] != "active": raise HTTPException(status_code=401, detail="API key is not active")
# Check required scopes if "read:users" not in verification["scopes"]: raise HTTPException(status_code=403, detail="Insufficient scope")
# Process request data = await get_data() return {"data": data, "key_id": verification["key_id"]}
except HTTPException: raise except Exception as error: logger.error(f"API key verification error: {error}") raise HTTPException(status_code=500, detail="Internal server error")
Kinde’s verification endpoint returns:
{ "code": "API_KEY_VERIFIED", "message": "API key verified", "is_valid": true, "key_id": "api_key_0195ac80a14e8d71f42b98e75d3c61ad", "status": "active", "scopes": ["read:users", "write:users"], "org_code": "org_1234567890", "user_id": "kp_1234567890", "last_verified_on": "2024-11-18T13:32:03+11", "verification_count": 42}
code
: Response code indicating the resultmessage
: Human-readable messageis_valid
: Boolean indicating if the key is validscopes
: Array of scopes the key has access tostatus
: Key status (active
, inactive
, revoked
)key_id
: Unique identifier for the keyorg_code
: Organization code if it’s an organization-level keyuser_id
: User ID if it’s a user-level keylast_verified_on
: When the key was last verifiedverification_count
: Number of times the key has been verifiedCreate reusable middleware for API key verification:
function requireApiKey(requiredScopes = []) { return async (req, res, next) => { try { const apiKey = extractApiKey(req); const verification = await verifyApiKey(apiKey);
if (!verification.is_valid) { return res.status(401).json({error: "Invalid API key"}); }
if (verification.status !== "active") { return res.status(401).json({error: "API key is not active"}); }
// Check required scopes for (const scope of requiredScopes) { if (!verification.scopes.includes(scope)) { return res.status(403).json({ error: `Insufficient scope. Required: ${scope}` }); } }
// Attach verification data to request req.apiKey = verification; next(); } catch (error) { console.error("API key middleware error:", error); res.status(500).json({error: "Internal server error"}); } };}
// Use middlewareapp.get("/api/data", requireApiKey(["read:users"]), (req, res) => { // req.apiKey contains verification data const data = getData(); res.json({data, key_id: req.apiKey.key_id});});
app.post("/api/data", requireApiKey(["read:users", "write:users"]), (req, res) => { // Requires both read and write scopes const result = createData(req.body); res.json({result, key_id: req.apiKey.key_id});});
Create a dependency for API key verification:
async def verify_api_key_dependency( authorization: str = Header(None), required_scopes: List[str] = []) -> dict: try: api_key = extract_api_key(authorization) verification = await verify_api_key(api_key)
if not verification["is_valid"]: raise HTTPException(status_code=401, detail="Invalid API key")
if verification["status"] != "active": raise HTTPException(status_code=401, detail="API key is not active")
# Check required scopes for scope in required_scopes: if scope not in verification["scopes"]: raise HTTPException( status_code=403, detail=f"Insufficient scope. Required: {scope}" )
return verification
except HTTPException: raise except Exception as error: logger.error(f"API key verification error: {error}") raise HTTPException(status_code=500, detail="Internal server error")
# Use dependency@app.get("/api/data")async def get_data(api_key: dict = Depends(verify_api_key_dependency)): data = await get_data() return {"data": data, "key_id": api_key["key_id"]}
@app.post("/api/data")async def create_data( data: dict, api_key: dict = Depends(lambda: verify_api_key_dependency(required_scopes=["read:users", "write:users"]))): result = await create_data(data) return {"result": result, "key_id": api_key["key_id"]}
Cache verification results to avoid repeated calls to Kinde:
class ApiKeyCache { constructor() { this.cache = new Map(); this.ttl = 5 * 60 * 1000; // 5 minutes }
get(key) { const cached = this.cache.get(key); if (cached && Date.now() - cached.timestamp < this.ttl) { return cached.data; } return null; }
set(key, data) { this.cache.set(key, { data, timestamp: Date.now() }); }
clear() { this.cache.clear(); }}
const apiKeyCache = new ApiKeyCache();
async function verifyApiKeyWithCache(apiKey) { // Check cache first const cached = apiKeyCache.get(apiKey); if (cached) { return cached; }
// Verify with Kinde const verification = await verifyApiKey(apiKey);
// Cache valid results if (verification.is_valid && verification.status === "active") { apiKeyCache.set(apiKey, verification); }
return verification;}
If you need to verify multiple keys, consider implementing batch verification:
async function verifyMultipleApiKeys(apiKeys) { // Note: This would require a batch endpoint from Kinde // For now, verify keys in parallel const verificationPromises = apiKeys.map((key) => verifyApiKey(key));
return Promise.all(verificationPromises);}
// Invalid API key{ "error": "Invalid API key", "code": "INVALID_KEY"}
// Insufficient scope{ "error": "Insufficient scope. Required: write:users", "code": "INSUFFICIENT_SCOPE", "required_scopes": ["write:users"], "available_scopes": ["read:users"]}
// Revoked key{ "error": "API key is not active", "code": "KEY_REVOKED"}
Implement rate limiting for verification attempts:
const rateLimit = require("express-rate-limit");
const verificationLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 verification attempts per window message: { error: "Too many verification attempts", code: "RATE_LIMITED" }});
app.use("/api/v1/api_keys/verify", verificationLimiter);
# Test with a valid API keycurl -X GET https://your-api.com/data \ -H "Authorization: Bearer YOUR_TEST_API_KEY"
# Test with invalid keycurl -X GET https://your-api.com/data \ -H "Authorization: Bearer invalid_key"
# Test with missing headercurl -X GET https://your-api.com/data
# Test with revoked keycurl -X GET https://your-api.com/data \ -H "Authorization: Bearer REVOKED_API_KEY"
// AI assistant using API key for customer supportapp.post("/ai/chat", async (req, res) => { try { const apiKey = extractApiKey(req); const verification = await verifyApiKey(apiKey);
if (!verification.is_valid) { return res.status(401).json({error: "Invalid API key"}); }
// Check if key has access to support data if (!verification.scopes.includes("read:tickets")) { return res.status(403).json({error: "Insufficient scope for support data"}); }
// AI can now access customer support data within the organization scope const customerData = await getCustomerSupportData(verification.org_code);
// Process AI request with customer context const aiResponse = await processAIRequest(req.body.message, customerData);
res.json({ response: aiResponse, context: `Organization: ${verification.org_code}`, key_id: verification.key_id }); } catch (error) { console.error("AI chat error:", error); res.status(500).json({error: "Internal server error"}); }});
// AI-powered workflow automationapp.post("/workflow/automate", async (req, res) => { try { const apiKey = extractApiKey(req); const verification = await verifyApiKey(apiKey);
if (!verification.is_valid) { return res.status(401).json({error: "Invalid API key"}); }
// AI workflow needs both read and write access const requiredScopes = ["read:users", "write:workflows"]; for (const scope of requiredScopes) { if (!verification.scopes.includes(scope)) { return res.status(403).json({ error: `Insufficient scope. Required: ${scope}` }); } }
// AI can now automate workflows within the organization const workflowResult = await executeAIWorkflow( req.body.workflow, verification.org_code, verification.user_id );
res.json({ result: workflowResult, automated_by: verification.key_id, organization: verification.org_code }); } catch (error) { console.error("Workflow automation error:", error); res.status(500).json({error: "Internal server error"}); }});