TypeScriptAuthentication

API Keys

Authenticate services and integrations with API keys

An API key is a long, random secret that a caller sends with every request. Keys suit services and integrations: another backend, an internal tool, a script. They don't identify a person and don't expire on their own, so for apps where people sign in, use JWT or OAuth 2.1 instead.

NitroStack's ApiKeyModule loads your valid keys and checks a key against them. You write a small guard that reads the key from the request and asks the module whether it's valid, then add that guard to the tools you want to protect.

Set it up

Add your keys to .env

Generate a key for each integration and store it in an environment variable. Every variable that starts with your prefix is loaded.

.env
API_KEY_1=sk_live_...
API_KEY_2=sk_live_...

Configure ApiKeyModule

Import the module in your root module. keysEnvPrefix: 'API_KEY' loads API_KEY_1, API_KEY_2, and so on.

src/app.module.ts
import { Module, ApiKeyModule } from '@nitrostack/core';

@Module({
  name: 'app',
  imports: [
    ApiKeyModule.forRoot({
      keysEnvPrefix: 'API_KEY',
    }),
  ],
})
export class AppModule {}

Write the guard

The guard reads the key from the request metadata, validates it with ApiKeyModule.validate(), and records who is calling on context.auth.

src/guards/apikey.guard.ts
import { Guard, ExecutionContext, ApiKeyModule } from '@nitrostack/core';

export class ApiKeyGuard implements Guard {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const apiKey = context.metadata?.apiKey || context.metadata?.['x-api-key'];
    if (!apiKey) {
      throw new Error('API key required');
    }

    const isValid = await ApiKeyModule.validate(apiKey as string);
    if (!isValid) {
      throw new Error('Invalid API key');
    }

    context.auth = {
      subject: `apikey_${(apiKey as string).substring(0, 12)}`,
      scopes: ['*'],
    };
    return true;
  }
}

Protect a tool

Tools without the guard stay public.

import { ToolDecorator as Tool, UseGuards, ExecutionContext } from '@nitrostack/core';
import { ApiKeyGuard } from './guards/apikey.guard.js';

export class DataTools {
  @Tool({ name: 'get_public_info', description: 'Public information' })
  async getPublicInfo() {
    return { data: 'public' };
  }

  @Tool({ name: 'get_protected_data', description: 'Protected data (requires an API key)' })
  @UseGuards(ApiKeyGuard)
  async getProtectedData(input: {}, ctx: ExecutionContext) {
    return { data: 'protected', accessedBy: ctx.auth?.subject };
  }
}

Options

ApiKeyModule.forRoot() accepts these options. Use keysEnvPrefix or keys to supply the valid keys.

OptionDefaultDescription
keysEnvPrefixLoads every environment variable named <prefix>_1, <prefix>_2, and so on
keysKeys listed directly. Avoid this outside local development
hashedfalseThe stored keys are SHA-256 hashes; incoming keys are hashed before comparison
headerName'x-api-key'HTTP header that carries the key
metadataField'apiKey'Field in the request's _meta that carries the key
customValidation(key) => boolean, an extra check, for example a database lookup

How clients send keys

Over HTTP, clients send the key in a header:

X-API-Key: sk_live_...

Clients can also put it in the request's _meta, which works over STDIO too:

{
  "method": "tools/call",
  "params": {
    "name": "get_protected_data",
    "arguments": { "_meta": { "apiKey": "sk_live_..." } }
  }
}

In NitroStudio, open Authentication, expand the project, and enter the key. Studio sends it with every call to that project.

Generate and store keys

ApiKeyModule.generateKey() creates a cryptographically random key with the prefix you choose. To keep plain keys out of your configuration, store their SHA-256 hashes and set hashed: true.

import { ApiKeyModule } from '@nitrostack/core';

const apiKey = ApiKeyModule.generateKey('sk');   // give this to the integration
const stored = ApiKeyModule.hashKey(apiKey);     // put this in API_KEY_1

In production, load keys from your platform's secret store into environment variables, as described in each cloud platform guide, rather than committing them anywhere.

Next steps

On this page