Skip to content
  • Manage your APIs
  • Add and manage API keys

Verify API keys in your API

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.

How API key verification works

Link to this section

The verification flow follows these steps:

  1. End user sends a request to your API with their API key
  2. Your API extracts the key and calls Kinde’s verification endpoint (this call requires an M2M token)
  3. Kinde validates the key and returns verification results
  4. Your API processes the request based on verification results

Basic implementation

Link to this section

Extract API key from request

Link to this section

First, extract the API key from the incoming request:

// Express.js example
function 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 example
def 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

Verify API key with Kinde

Link to this section

Call Kinde’s verification endpoint to validate the key:

// Node.js/Express example
async 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 example
async 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()

Implement in your API endpoint

Link to this section

Use the verification in your API endpoints:

// Express.js endpoint
app.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")

Verification response

Link to this section

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
}

Response fields

Link to this section
  • code: Response code indicating the result
  • message: Human-readable message
  • is_valid: Boolean indicating if the key is valid
  • scopes: Array of scopes the key has access to
  • status: Key status (active, inactive, revoked)
  • key_id: Unique identifier for the key
  • org_code: Organization code if it’s an organization-level key
  • user_id: User ID if it’s a user-level key
  • last_verified_on: When the key was last verified
  • verification_count: Number of times the key has been verified

Advanced implementation

Link to this section

Middleware for Express.js

Link to this section

Create 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 middleware
app.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});
});

Dependency injection for FastAPI

Link to this section

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"]}

Performance optimization

Link to this section

Implement caching

Link to this section

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;
}

Batch verification

Link to this section

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);
}

Error handling

Link to this section

Common error responses

Link to this section
// 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);

Recommendations

Link to this section

Secure API key verification

Link to this section
  • HTTPS only: Always use HTTPS for verification requests
  • Input validation: Validate the API key format before sending to Kinde
  • Error handling: Don’t expose sensitive information in error messages

Verify and validate

Link to this section
  • Always validate the verification response from Kinde
  • Check all required fields before processing requests
  • Implement proper error handling for verification failures

Monitor verification activity

Link to this section
  • Log verification attempts (without sensitive data)
  • Monitor for unusual verification patterns
  • Set up alerts for verification failures

Test the API key validation process

Link to this section

Test with valid keys

Link to this section
Terminal window
# Test with a valid API key
curl -X GET https://your-api.com/data \
-H "Authorization: Bearer YOUR_TEST_API_KEY"

Test error cases

Link to this section
Terminal window
# Test with invalid key
curl -X GET https://your-api.com/data \
-H "Authorization: Bearer invalid_key"
# Test with missing header
curl -X GET https://your-api.com/data
# Test with revoked key
curl -X GET https://your-api.com/data \
-H "Authorization: Bearer REVOKED_API_KEY"

Verification for AI applications

Link to this section

Validate API keys for AI assistant integration

Link to this section
// AI assistant using API key for customer support
app.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"});
}
});

Validate API keys for automated workflow with AI

Link to this section
// AI-powered workflow automation
app.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"});
}
});