Nest Authbeta

API Keys

Server-to-server and programmatic access via public/private key pairs.

For machine-to-machine auth — CLIs, integrations, internal tooling — Nest Auth ships an API key system. Each key is a pair: a non-secret public ID and a secret private value. The pair is bound to a user; calls authenticated with the key act as that user.

How calls authenticate

GET /api/orders HTTP/1.1
Authorization: ApiKey <publicKey>.<privateKey>

NestAuthAuthGuard reads the scheme: Bearer is a JWT, ApiKey is a key pair (public and private joined by a dot).

Generating a key

import { AccessKeyService } from '@ackplus/nest-auth';
 
@Injectable()
export class IntegrationsService {
  constructor(private readonly keys: AccessKeyService) {}
 
  async issueKey(userId: string, tenantId?: string) {
    const key = await this.keys.createAccessKey(userId, 'Slack integration', { tenantId });
    await this.keys.setAccessKeyExpiry(key.publicKey, new Date(Date.now() + 90 * 86400_000));
 
    // Show key.privateKey to the user ONCE. Only its hash is stored.
    return key;
  }
}

privateKey is returned only once, at creation. After that the library holds only a hash.

Lifecycle

OperationMethod
CreatecreateAccessKey(userId, name, { tenantId? })
List for usergetUserAccessKeys(userId)
Set expirysetAccessKeyExpiry(publicKey, expiresAt)
DeactivatedeactivateAccessKey(publicKey) (key still exists, but rejects auth)
DeletedeleteAccessKey(publicKey)

Lifecycle operations emit NestAuthEvents.ACCESS_KEY_* events — wire them to your audit log.

What a key request looks like to your app

Since 2.13, a request authenticated by key carries a session for the key's user, built fresh for that request. So RequestContext.currentUserId(), currentTenantId(), currentUserAccess() and request.session.data work exactly as they do for a signed-in user, and your own guards and services can tenant-scope a key request without special-casing it.

const session = RequestContext.currentSession();
session.data.authType;     // 'api-key'
session.data.accessKeyId;  // the key that authenticated
session.data.tenantId;     // see "Which tenant" below
session.id;                // undefined — see below

That session is never stored: it has no id, it is rebuilt from the live rows on every request (so it cannot go stale), and it cannot be refreshed, switched or logged out. Endpoints that change a stored session (logout, switch-tenant, MFA) treat a key request as having no session.

Which tenant

KeyUser's active membershipstenantId
Pinned ({ tenantId } at creation)—the pinned tenant
Unpinnedexactly onethat membership's tenant
Unpinnedseveralnull — pin the key to choose one

In ISOLATED mode every user has exactly one membership, so an unpinned key is always scoped. In SHARED mode, pin keys for users who belong to more than one tenant. A key can only be pinned to a tenant its user is an active member of (400 API_KEY_TENANT_MISMATCH otherwise).

Roles & permissions

A key acts with the roles of that one membership — the same rows sign-in reads — and only while the membership is active. A pinned key whose membership has been deactivated keeps its tenant but carries no grants. (An unpinned key for a multi-tenant user still gets the union of roles across its active memberships, as before.)

A key also stops working when its owner is deactivated (401 ACCOUNT_INACTIVE), not only when the key itself is.

Turning API keys off

NestAuthModule.forRoot({
  accessKeys: { enabled: false },
});

The guard then rejects every ApiKey credential with 401 API_KEY_AUTH_DISABLED (optional-auth routes proceed unauthenticated), and createAccessKey refuses to mint new keys. Default is true.

Key validation flow

  1. Guard checks accessKeys.enabled, then parses Authorization: ApiKey <publicKey>.<privateKey>.
  2. Looks up nest_auth_access_keys by publicKey; checks isActive and expiresAt.
  3. Compares the hash of the supplied private key against the stored hash (constant-time).
  4. Checks the owning user is active.
  5. Updates lastUsedAt and builds the request's session.

A bad key returns 401 INVALID_API_KEY.

On this page