[PR #3771] [CLOSED] feature: M2M Authorization #21897

Closed
opened 2026-04-15 20:41:02 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/3771
Author: @Nitish-Naik
Created: 8/4/2025
Status: Closed

Base: canaryHead: plugin-for-Machine-to-Machine-(M2M)-Authorization


📝 Commits (1)

  • 6e739f8 feature: M2M Authorization

📊 Changes

21 files changed (+2423 additions, -55 deletions)

View changed files

M2M_IMPLEMENTATION_SUMMARY.md (+257 -0)
demo/nextjs/.env.example (+0 -23)
📝 demo/nextjs/lib/auth.ts (+28 -28)
📝 docs/app/blog/[[...slug]]/page.tsx (+3 -1)
📝 docs/app/changelogs/[[...slug]]/page.tsx (+3 -1)
📝 docs/app/docs/[[...slug]]/page.tsx (+3 -1)
📝 docs/lib/utils.ts (+2 -1)
examples/m2m-example/README.md (+182 -0)
examples/m2m-example/app/api/m2m/clients/route.ts (+33 -0)
examples/m2m-example/app/api/m2m/token/route.ts (+15 -0)
examples/m2m-example/app/page.tsx (+294 -0)
examples/m2m-example/lib/auth.ts (+27 -0)
examples/m2m-example/package.json (+26 -0)
📝 packages/better-auth/src/plugins/index.ts (+1 -0)
packages/better-auth/src/plugins/m2m/README.md (+314 -0)
packages/better-auth/src/plugins/m2m/client.ts (+73 -0)
packages/better-auth/src/plugins/m2m/index.ts (+246 -0)
packages/better-auth/src/plugins/m2m/m2m.test.ts (+340 -0)
packages/better-auth/src/plugins/m2m/routes.ts (+358 -0)
packages/better-auth/src/plugins/m2m/schema.ts (+30 -0)

...and 1 more files

📄 Description

Added a plugin for Machine-to-Machine (M2M) Authorization #676

M2M (Machine-to-Machine) Plugin

The M2M plugin provides OAuth 2.0 Client Credentials Grant flow for machine-to-machine authentication. This allows servers and automated processes to authenticate with your API without user interaction.

Features

  • OAuth 2.0 Client Credentials Grant: Standard-compliant M2M authentication
  • Client Management: Create, list, update, and delete M2M clients
  • Scope-based Authorization: Control what each client can access
  • Client Expiration: Set expiration dates for clients
  • Metadata Support: Store additional information about clients
  • Rate Limiting: Built-in rate limiting for token requests
  • Secure Secret Storage: Client secrets are hashed by default

Installation

The M2M plugin is included with Better-Auth. No additional installation is required.

Basic Usage

1. Add the Plugin to Your Auth Configuration

import { createAuth } from "better-auth";
import { m2m } from "better-auth/plugins/m2m";

const auth = createAuth({
  adapter: yourAdapter,
  plugins: [
    m2m({
      // Optional configuration
      enableMetadata: true,
      requireClientName: false,
    }),
  ],
});

2. Create an M2M Client

// Create a new M2M client
const response = await auth.api.post("/m2m/clients", {
  name: "My API Server",
  scopes: ["read", "write"],
  metadata: { environment: "production" },
  expiresIn: 365, // Expires in 365 days
});

console.log(response.data);
// {
//   id: "client_123",
//   clientId: "abc123def456",
//   clientSecret: "xyz789...", // Only returned once!
//   name: "My API Server",
//   scopes: ["read", "write"],
//   metadata: { environment: "production" },
//   expiresAt: "2025-01-01T00:00:00.000Z",
//   createdAt: "2024-01-01T00:00:00.000Z"
// }

3. Get an Access Token

// From your API server, request an access token
const tokenResponse = await fetch("https://your-auth-server.com/m2m/token", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded",
  },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    client_id: "abc123def456",
    client_secret: "xyz789...",
    scope: "read write",
  }),
});

const tokenData = await tokenResponse.json();
// {
//   access_token: "eyJhbGciOiJIUzI1NiIs...",
//   token_type: "bearer",
//   expires_in: 3600,
//   refresh_token: "def456ghi789...",
//   scope: "read write"
// }

4. Use the Access Token

// Use the access token to make authenticated requests
const apiResponse = await fetch("https://your-api-server.com/protected-endpoint", {
  headers: {
    Authorization: `Bearer ${tokenData.access_token}`,
  },
});

API Endpoints

Token Endpoint

POST /m2m/token

Get an access token using client credentials.

Request Body:

{
  grant_type: "client_credentials",
  client_id: string,
  client_secret: string,
  scope?: string // Optional, space-separated scopes
}

Response:

{
  access_token: string,
  token_type: "bearer",
  expires_in: number,
  refresh_token: string,
  scope: string
}

Client Management Endpoints

POST /m2m/clients - Create a new M2M client
GET /m2m/clients - List all M2M clients
GET /m2m/clients/:id - Get a specific M2M client
PUT /m2m/clients/:id - Update an M2M client
DELETE /m2m/clients/:id - Delete an M2M client

Configuration Options

m2m({
  // Client secret configuration
  defaultClientSecretLength: 64,
  disableClientSecretHashing: false,

  // Client name configuration
  requireClientName: false,
  maximumClientNameLength: 100,
  minimumClientNameLength: 1,

  // Metadata configuration
  enableMetadata: false,

  // Rate limiting
  rateLimit: {
    enabled: true,
    timeWindow: 24 * 60 * 60 * 1000, // 24 hours
    maxRequests: 1000,
  },

  // Client expiration
  clientExpiration: {
    defaultExpiresIn: null, // No default expiration
    disableCustomExpiresTime: false,
    maxExpiresIn: 365, // Maximum 365 days
    minExpiresIn: 1, // Minimum 1 day
  },

  // Token expiration
  accessTokenExpiresIn: 3600, // 1 hour
  refreshTokenExpiresIn: 2592000, // 30 days

  // Starting characters for display
  startingCharactersConfig: {
    shouldStore: true,
    charactersLength: 6,
  },
})

Client Utilities

The plugin provides client utilities for easier management:

import { m2mClient } from "better-auth/plugins/m2m/client";

const client = m2mClient(auth);

// Create a client
const newClient = await client.createClient({
  name: "My Server",
  scopes: ["read"],
});

// List clients
const clients = await client.listClients({ limit: 10, offset: 0 });

// Get a specific client
const clientDetails = await client.getClient("client_id");

// Update a client
await client.updateClient("client_id", {
  name: "Updated Name",
  disabled: false,
});

// Delete a client
await client.deleteClient("client_id");

// Get access token
const token = await client.getAccessToken({
  clientId: "abc123",
  clientSecret: "xyz789",
  scope: "read",
});

Security Considerations

  1. Client Secret Storage: Client secrets are hashed by default. Store the plain secret securely when first created.

  2. Scope Validation: Always validate scopes to ensure clients only access what they're authorized for.

  3. Client Expiration: Use client expiration to limit the lifetime of credentials.

  4. Rate Limiting: Enable rate limiting to prevent abuse.

  5. HTTPS Only: Always use HTTPS in production to protect credentials in transit.

Error Handling

The plugin returns standard OAuth 2.0 error responses:

// Invalid client
{
  error: "invalid_client",
  error_description: "Invalid client ID"
}

// Invalid scope
{
  error: "invalid_scope",
  error_description: "Invalid scope"
}

// Unsupported grant type
{
  error: "unsupported_grant_type",
  error_description: "grant_type must be 'client_credentials'"
}

Use Cases

  • API-to-API Communication: When one service needs to call another
  • Background Jobs: Automated processes that need API access
  • Microservices: Service-to-service authentication
  • Webhooks: Secure webhook delivery
  • Cron Jobs: Scheduled tasks that need API access

Example: Complete Setup

// auth-server.ts
import { createAuth } from "better-auth";
import { m2m } from "better-auth/plugins/m2m";
import { libsqlAdapter } from "better-auth/adapters/libsql";

const auth = createAuth({
  adapter: libsqlAdapter({
    url: "file:./auth.db",
  }),
  plugins: [
    m2m({
      enableMetadata: true,
      requireClientName: true,
      clientExpiration: {
        defaultExpiresIn: 365,
        maxExpiresIn: 730,
      },
    }),
  ],
});

// api-server.ts
import { verifyToken } from "better-auth";

// Middleware to verify M2M tokens
async function verifyM2MToken(req, res, next) {
  const token = req.headers.authorization?.replace("Bearer ", "");
  
  if (!token) {
    return res.status(401).json({ error: "No token provided" });
  }

  try {
    const decoded = await verifyToken(token, auth);
    req.m2mClient = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ error: "Invalid token" });
  }
}

// Protected endpoint
app.get("/api/data", verifyM2MToken, (req, res) => {
  // req.m2mClient contains the client information
  res.json({ data: "Protected data" });
});

This plugin provides a complete solution for machine-to-machine authentication using OAuth 2.0 Client Credentials Grant flow, making it easy to secure API-to-API communication in your applications.


Summary by cubic

Added a plugin for Machine-to-Machine (M2M) Authorization, enabling OAuth 2.0 Client Credentials Grant flow for secure server-to-server authentication and automated API access.

  • New Features
    • Supports creating, listing, updating, and deleting M2M clients with scope-based permissions, expiration, and optional metadata.
    • Provides secure client secret handling, rate limiting, and standard OAuth 2.0 token endpoints.
    • Includes client utilities, example app, and full documentation for easy integration.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/3771 **Author:** [@Nitish-Naik](https://github.com/Nitish-Naik) **Created:** 8/4/2025 **Status:** ❌ Closed **Base:** `canary` ← **Head:** `plugin-for-Machine-to-Machine-(M2M)-Authorization` --- ### 📝 Commits (1) - [`6e739f8`](https://github.com/better-auth/better-auth/commit/6e739f8dcc9582396c8569355770198a33d4285f) feature: M2M Authorization ### 📊 Changes **21 files changed** (+2423 additions, -55 deletions) <details> <summary>View changed files</summary> ➕ `M2M_IMPLEMENTATION_SUMMARY.md` (+257 -0) ➖ `demo/nextjs/.env.example` (+0 -23) 📝 `demo/nextjs/lib/auth.ts` (+28 -28) 📝 `docs/app/blog/[[...slug]]/page.tsx` (+3 -1) 📝 `docs/app/changelogs/[[...slug]]/page.tsx` (+3 -1) 📝 `docs/app/docs/[[...slug]]/page.tsx` (+3 -1) 📝 `docs/lib/utils.ts` (+2 -1) ➕ `examples/m2m-example/README.md` (+182 -0) ➕ `examples/m2m-example/app/api/m2m/clients/route.ts` (+33 -0) ➕ `examples/m2m-example/app/api/m2m/token/route.ts` (+15 -0) ➕ `examples/m2m-example/app/page.tsx` (+294 -0) ➕ `examples/m2m-example/lib/auth.ts` (+27 -0) ➕ `examples/m2m-example/package.json` (+26 -0) 📝 `packages/better-auth/src/plugins/index.ts` (+1 -0) ➕ `packages/better-auth/src/plugins/m2m/README.md` (+314 -0) ➕ `packages/better-auth/src/plugins/m2m/client.ts` (+73 -0) ➕ `packages/better-auth/src/plugins/m2m/index.ts` (+246 -0) ➕ `packages/better-auth/src/plugins/m2m/m2m.test.ts` (+340 -0) ➕ `packages/better-auth/src/plugins/m2m/routes.ts` (+358 -0) ➕ `packages/better-auth/src/plugins/m2m/schema.ts` (+30 -0) _...and 1 more files_ </details> ### 📄 Description Added a plugin for Machine-to-Machine (M2M) Authorization #676 # M2M (Machine-to-Machine) Plugin The M2M plugin provides OAuth 2.0 Client Credentials Grant flow for machine-to-machine authentication. This allows servers and automated processes to authenticate with your API without user interaction. ## Features - **OAuth 2.0 Client Credentials Grant**: Standard-compliant M2M authentication - **Client Management**: Create, list, update, and delete M2M clients - **Scope-based Authorization**: Control what each client can access - **Client Expiration**: Set expiration dates for clients - **Metadata Support**: Store additional information about clients - **Rate Limiting**: Built-in rate limiting for token requests - **Secure Secret Storage**: Client secrets are hashed by default ## Installation The M2M plugin is included with Better-Auth. No additional installation is required. ## Basic Usage ### 1. Add the Plugin to Your Auth Configuration ```typescript import { createAuth } from "better-auth"; import { m2m } from "better-auth/plugins/m2m"; const auth = createAuth({ adapter: yourAdapter, plugins: [ m2m({ // Optional configuration enableMetadata: true, requireClientName: false, }), ], }); ``` ### 2. Create an M2M Client ```typescript // Create a new M2M client const response = await auth.api.post("/m2m/clients", { name: "My API Server", scopes: ["read", "write"], metadata: { environment: "production" }, expiresIn: 365, // Expires in 365 days }); console.log(response.data); // { // id: "client_123", // clientId: "abc123def456", // clientSecret: "xyz789...", // Only returned once! // name: "My API Server", // scopes: ["read", "write"], // metadata: { environment: "production" }, // expiresAt: "2025-01-01T00:00:00.000Z", // createdAt: "2024-01-01T00:00:00.000Z" // } ``` ### 3. Get an Access Token ```typescript // From your API server, request an access token const tokenResponse = await fetch("https://your-auth-server.com/m2m/token", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ grant_type: "client_credentials", client_id: "abc123def456", client_secret: "xyz789...", scope: "read write", }), }); const tokenData = await tokenResponse.json(); // { // access_token: "eyJhbGciOiJIUzI1NiIs...", // token_type: "bearer", // expires_in: 3600, // refresh_token: "def456ghi789...", // scope: "read write" // } ``` ### 4. Use the Access Token ```typescript // Use the access token to make authenticated requests const apiResponse = await fetch("https://your-api-server.com/protected-endpoint", { headers: { Authorization: `Bearer ${tokenData.access_token}`, }, }); ``` ## API Endpoints ### Token Endpoint **POST** `/m2m/token` Get an access token using client credentials. **Request Body:** ```typescript { grant_type: "client_credentials", client_id: string, client_secret: string, scope?: string // Optional, space-separated scopes } ``` **Response:** ```typescript { access_token: string, token_type: "bearer", expires_in: number, refresh_token: string, scope: string } ``` ### Client Management Endpoints **POST** `/m2m/clients` - Create a new M2M client **GET** `/m2m/clients` - List all M2M clients **GET** `/m2m/clients/:id` - Get a specific M2M client **PUT** `/m2m/clients/:id` - Update an M2M client **DELETE** `/m2m/clients/:id` - Delete an M2M client ## Configuration Options ```typescript m2m({ // Client secret configuration defaultClientSecretLength: 64, disableClientSecretHashing: false, // Client name configuration requireClientName: false, maximumClientNameLength: 100, minimumClientNameLength: 1, // Metadata configuration enableMetadata: false, // Rate limiting rateLimit: { enabled: true, timeWindow: 24 * 60 * 60 * 1000, // 24 hours maxRequests: 1000, }, // Client expiration clientExpiration: { defaultExpiresIn: null, // No default expiration disableCustomExpiresTime: false, maxExpiresIn: 365, // Maximum 365 days minExpiresIn: 1, // Minimum 1 day }, // Token expiration accessTokenExpiresIn: 3600, // 1 hour refreshTokenExpiresIn: 2592000, // 30 days // Starting characters for display startingCharactersConfig: { shouldStore: true, charactersLength: 6, }, }) ``` ## Client Utilities The plugin provides client utilities for easier management: ```typescript import { m2mClient } from "better-auth/plugins/m2m/client"; const client = m2mClient(auth); // Create a client const newClient = await client.createClient({ name: "My Server", scopes: ["read"], }); // List clients const clients = await client.listClients({ limit: 10, offset: 0 }); // Get a specific client const clientDetails = await client.getClient("client_id"); // Update a client await client.updateClient("client_id", { name: "Updated Name", disabled: false, }); // Delete a client await client.deleteClient("client_id"); // Get access token const token = await client.getAccessToken({ clientId: "abc123", clientSecret: "xyz789", scope: "read", }); ``` ## Security Considerations 1. **Client Secret Storage**: Client secrets are hashed by default. Store the plain secret securely when first created. 2. **Scope Validation**: Always validate scopes to ensure clients only access what they're authorized for. 3. **Client Expiration**: Use client expiration to limit the lifetime of credentials. 4. **Rate Limiting**: Enable rate limiting to prevent abuse. 5. **HTTPS Only**: Always use HTTPS in production to protect credentials in transit. ## Error Handling The plugin returns standard OAuth 2.0 error responses: ```typescript // Invalid client { error: "invalid_client", error_description: "Invalid client ID" } // Invalid scope { error: "invalid_scope", error_description: "Invalid scope" } // Unsupported grant type { error: "unsupported_grant_type", error_description: "grant_type must be 'client_credentials'" } ``` ## Use Cases - **API-to-API Communication**: When one service needs to call another - **Background Jobs**: Automated processes that need API access - **Microservices**: Service-to-service authentication - **Webhooks**: Secure webhook delivery - **Cron Jobs**: Scheduled tasks that need API access ## Example: Complete Setup ```typescript // auth-server.ts import { createAuth } from "better-auth"; import { m2m } from "better-auth/plugins/m2m"; import { libsqlAdapter } from "better-auth/adapters/libsql"; const auth = createAuth({ adapter: libsqlAdapter({ url: "file:./auth.db", }), plugins: [ m2m({ enableMetadata: true, requireClientName: true, clientExpiration: { defaultExpiresIn: 365, maxExpiresIn: 730, }, }), ], }); // api-server.ts import { verifyToken } from "better-auth"; // Middleware to verify M2M tokens async function verifyM2MToken(req, res, next) { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) { return res.status(401).json({ error: "No token provided" }); } try { const decoded = await verifyToken(token, auth); req.m2mClient = decoded; next(); } catch (error) { return res.status(401).json({ error: "Invalid token" }); } } // Protected endpoint app.get("/api/data", verifyM2MToken, (req, res) => { // req.m2mClient contains the client information res.json({ data: "Protected data" }); }); ``` This plugin provides a complete solution for machine-to-machine authentication using OAuth 2.0 Client Credentials Grant flow, making it easy to secure API-to-API communication in your applications. <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Added a plugin for Machine-to-Machine (M2M) Authorization, enabling OAuth 2.0 Client Credentials Grant flow for secure server-to-server authentication and automated API access. - **New Features** - Supports creating, listing, updating, and deleting M2M clients with scope-based permissions, expiration, and optional metadata. - Provides secure client secret handling, rate limiting, and standard OAuth 2.0 token endpoints. - Includes client utilities, example app, and full documentation for easy integration. <!-- End of auto-generated description by cubic. --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-15 20:41:02 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#21897