Machine-to-machine (M2M) authentication enables secure communication between services, APIs, and automated processes without user interaction. Stack Auth provides service accounts and API keys for M2M authentication.
Features
Service accounts for automated processes
API key authentication
Fine-grained permissions and scopes
Token-based authentication
No user interaction required
Audit logging for service account actions
Use Cases
M2M authentication is ideal for:
Backend services - Server-to-server API calls
Microservices - Service mesh authentication
CI/CD pipelines - Automated deployments and testing
Scheduled jobs - Cron jobs and background tasks
Data synchronization - ETL processes and data pipelines
Third-party integrations - External service communication
IoT devices - Device authentication and communication
Service Accounts
Service accounts are special accounts designed for automated processes:
Create Service Account
import { stackServerApp } from "@/stack" ;
const serviceAccount = await stackServerApp . createServiceAccount ({
name: "Data Sync Service" ,
description: "Service account for daily data synchronization" ,
permissions: [ "users:read" , "users:write" ],
scopes: [ "api:full" ]
});
// Returns
{
id : "sa_1234567890" ,
name : "Data Sync Service" ,
apiKey : "sa_live_abcdef123456..." , // Store securely!
createdAt : "2026-03-03T12:00:00Z"
}
API keys are only shown once during creation. Store them securely in a secrets manager or environment variables.
List Service Accounts
const serviceAccounts = await stackServerApp . listServiceAccounts ();
serviceAccounts . forEach ( account => {
console . log ( ` ${ account . name } : ${ account . id } ` );
});
Update Service Account
await stackServerApp . updateServiceAccount ( "sa_1234567890" , {
name: "Updated Service Name" ,
permissions: [ "users:read" ], // Reduced permissions
});
Delete Service Account
await stackServerApp . deleteServiceAccount ( "sa_1234567890" );
API Key Authentication
Server-Side Usage
Authenticate API requests using the service account API key:
import { StackServerApp } from "@stackframe/stack" ;
const serviceApp = new StackServerApp ({
apiKey: process . env . SERVICE_ACCOUNT_API_KEY ,
projectId: "your_project_id"
});
// Make authenticated requests
const users = await serviceApp . listUsers ();
HTTP API Requests
Use the API key in HTTP requests:
GET / api / v1 / users
Authorization : Bearer sa_live_abcdef123456 ...
Content - Type : application / json
Example with fetch:
const response = await fetch ( "https://api.stack-auth.com/api/v1/users" , {
headers: {
"Authorization" : `Bearer ${ process . env . SERVICE_ACCOUNT_API_KEY } ` ,
"Content-Type" : "application/json"
}
});
const users = await response . json ();
Permissions and Scopes
Service accounts can be granted specific permissions:
Available Permissions
User Permissions
Team Permissions
Project Permissions
API Scopes
// Read user data
"users:read"
// Create and update users
"users:write"
// Delete users
"users:delete"
// Full user management
"users:*"
Grant Permissions
await stackServerApp . updateServiceAccount ( "sa_1234567890" , {
permissions: [
"users:read" ,
"users:write" ,
"teams:read"
]
});
Token Exchange
Exchange service account API keys for short-lived access tokens:
Request Access Token
POST / api / v1 / auth / token
Content - Type : application / json
{
"grant_type" : "client_credentials" ,
"client_id" : "sa_1234567890" ,
"client_secret" : "sa_live_abcdef123456..." ,
"scope" : "users:read teams:read"
}
Response:
{
"access_token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"token_type" : "Bearer" ,
"expires_in" : 3600 ,
"scope" : "users:read teams:read"
}
Use Access Token
const response = await fetch ( "https://api.stack-auth.com/api/v1/users" , {
headers: {
"Authorization" : `Bearer ${ accessToken } ` ,
"Content-Type" : "application/json"
}
});
Common Integration Patterns
Microservices Communication
// Service A calls Service B
import { StackServerApp } from "@stackframe/stack" ;
class ServiceClient {
private stackApp : StackServerApp ;
constructor () {
this . stackApp = new StackServerApp ({
apiKey: process . env . SERVICE_ACCOUNT_API_KEY ,
projectId: process . env . STACK_PROJECT_ID
});
}
async callServiceB ( userId : string ) {
// Authenticate using service account
const user = await this . stackApp . getUser ( userId );
// Call Service B with authenticated context
return await fetch ( `https://service-b/api/process` , {
method: "POST" ,
headers: {
"Authorization" : `Bearer ${ process . env . SERVICE_ACCOUNT_API_KEY } ` ,
"Content-Type" : "application/json"
},
body: JSON . stringify ({ user })
});
}
}
CI/CD Pipeline
# .github/workflows/deploy.yml
name : Deploy
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v2
- name : Setup Stack Auth
env :
STACK_SERVICE_ACCOUNT_KEY : ${{ secrets.STACK_SERVICE_ACCOUNT_KEY }}
run : |
npm install @stackframe/stack
- name : Create deployment user
env :
STACK_SERVICE_ACCOUNT_KEY : ${{ secrets.STACK_SERVICE_ACCOUNT_KEY }}
run : |
node scripts/create-deployment-user.js
create-deployment-user.js:
import { StackServerApp } from "@stackframe/stack" ;
const stackApp = new StackServerApp ({
apiKey: process . env . STACK_SERVICE_ACCOUNT_KEY ,
projectId: process . env . STACK_PROJECT_ID
});
const deploymentUser = await stackApp . createUser ({
email: `deploy- ${ Date . now () } @internal.company.com` ,
displayName: "Deployment Bot" ,
metadata: {
createdBy: "CI/CD" ,
deploymentId: process . env . GITHUB_RUN_ID
}
});
console . log ( "Created deployment user:" , deploymentUser . id );
Scheduled Jobs
// cron/daily-sync.ts
import { StackServerApp } from "@stackframe/stack" ;
import { CronJob } from "cron" ;
const stackApp = new StackServerApp ({
apiKey: process . env . SERVICE_ACCOUNT_API_KEY ,
projectId: process . env . STACK_PROJECT_ID
});
const dailySync = new CronJob (
"0 0 * * *" , // Run daily at midnight
async () => {
console . log ( "Starting daily user sync..." );
const users = await stackApp . listUsers ();
for ( const user of users ) {
await syncUserToDataWarehouse ( user );
}
console . log ( `Synced ${ users . length } users` );
},
null ,
true ,
"America/New_York"
);
async function syncUserToDataWarehouse ( user ) {
// Sync logic here
}
IoT Device Authentication
// iot-device.ts
import { StackServerApp } from "@stackframe/stack" ;
class IoTDevice {
private stackApp : StackServerApp ;
private deviceId : string ;
constructor ( deviceId : string , apiKey : string ) {
this . deviceId = deviceId ;
this . stackApp = new StackServerApp ({
apiKey ,
projectId: process . env . STACK_PROJECT_ID
});
}
async reportTelemetry ( data : any ) {
// Store telemetry in user metadata
await this . stackApp . updateUser ( this . deviceId , {
metadata: {
lastTelemetry: data ,
lastSeen: new Date (). toISOString ()
}
});
}
async getConfiguration () {
const device = await this . stackApp . getUser ( this . deviceId );
return device . metadata ?. configuration ?? {};
}
}
// Usage
const device = new IoTDevice (
"device_123" ,
process . env . IOT_SERVICE_ACCOUNT_KEY
);
await device . reportTelemetry ({
temperature: 72.5 ,
humidity: 45.2 ,
timestamp: Date . now ()
});
API Key Rotation
Regularly rotate API keys for security:
// 1. Create new service account
const newAccount = await stackServerApp . createServiceAccount ({
name: serviceAccount . name + " (Rotated)" ,
permissions: serviceAccount . permissions
});
// 2. Update environment variables with new key
process . env . SERVICE_ACCOUNT_API_KEY = newAccount . apiKey ;
// 3. Deploy updated configuration
await deployConfiguration ();
// 4. Delete old service account after verification
await stackServerApp . deleteServiceAccount ( serviceAccount . id );
Audit Logging
Monitor service account activity:
const auditLogs = await stackServerApp . getAuditLogs ({
serviceAccountId: "sa_1234567890" ,
startDate: new Date ( "2026-03-01" ),
endDate: new Date ( "2026-03-03" )
});
auditLogs . forEach ( log => {
console . log ( ` ${ log . timestamp } : ${ log . action } by ${ log . serviceAccountId } ` );
});
Security Best Practices
Store keys securely - Use environment variables or secrets managers
Principle of least privilege - Grant minimal required permissions
Rotate keys regularly - Implement key rotation every 90 days
Monitor usage - Set up alerts for unusual activity
Use short-lived tokens - Exchange API keys for access tokens when possible
Separate accounts - Use different service accounts for different services
Revoke compromised keys - Immediately delete compromised service accounts
Audit regularly - Review service account permissions quarterly
Environment Configuration
# .env
STACK_PROJECT_ID = your_project_id
STACK_SERVICE_ACCOUNT_KEY = sa_live_abcdef123456...
# Optional: Use different keys per environment
STACK_SERVICE_ACCOUNT_KEY_DEV = sa_dev_...
STACK_SERVICE_ACCOUNT_KEY_STAGING = sa_staging_...
STACK_SERVICE_ACCOUNT_KEY_PROD = sa_prod_...
Usage:
import { StackServerApp } from "@stackframe/stack" ;
const env = process . env . NODE_ENV || "development" ;
const apiKey = {
development: process . env . STACK_SERVICE_ACCOUNT_KEY_DEV ,
staging: process . env . STACK_SERVICE_ACCOUNT_KEY_STAGING ,
production: process . env . STACK_SERVICE_ACCOUNT_KEY_PROD
}[ env ];
const stackApp = new StackServerApp ({
apiKey ,
projectId: process . env . STACK_PROJECT_ID
});
Error Handling
Invalid API Key
Insufficient Permissions
Rate Limited
try {
const users = await stackApp . listUsers ();
} catch ( error ) {
if ( error . code === "INVALID_API_KEY" ) {
console . error ( "Service account API key is invalid or expired" );
}
}
Limitations
Service accounts cannot sign in through the UI
Service accounts don’t have sessions or refresh tokens
Maximum 100 service accounts per project
API key length is fixed at 64 characters
Service accounts cannot have MFA enabled
Migration from API Keys
If you’re currently using project API keys, migrate to service accounts:
// Old approach (deprecated)
const stackApp = new StackServerApp ({
secretServerKey: process . env . STACK_SECRET_SERVER_KEY
});
// New approach (recommended)
const stackApp = new StackServerApp ({
apiKey: process . env . STACK_SERVICE_ACCOUNT_KEY ,
projectId: process . env . STACK_PROJECT_ID
});
API Reference
Key endpoints for M2M authentication:
POST /api/v1/service-accounts - Create service account
GET /api/v1/service-accounts - List service accounts
PATCH /api/v1/service-accounts/{id} - Update service account
DELETE /api/v1/service-accounts/{id} - Delete service account
POST /api/v1/auth/token - Exchange credentials for access token
Resources