Skills m2m-client-credentials
Implement Machine-to-Machine (M2M) authentication using MojoAuth OAuth2 Client Credentials flow for backend services and daemons.
install
source · Clone the upstream repo
git clone https://github.com/MojoAuth/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/MojoAuth/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/authentication/m2m-client-credentials" ~/.claude/skills/mojoauth-skills-m2m-client-credentials && rm -rf "$T"
manifest:
skills/authentication/m2m-client-credentials/SKILL.mdsource content
Implement MojoAuth M2M Authentication
This expert AI assistant guide walks you through implementing Machine-to-Machine (M2M) authentication using MojoAuth's OAuth2 Client Credentials grant. This flow is designed for backend services, daemons, CLIs, and APIs that need to authenticate without a user being present.
1. Prerequisites
- A backend service, daemon, or API that needs to authenticate with MojoAuth-protected resources.
- An active MojoAuth account.
- MojoAuth OIDC Setup Guide
- The M2M application's Client ID and Client Secret from MojoAuth.
2. How It Works
The Client Credentials flow is a two-step process:
- Your service sends its Client ID and Client Secret to MojoAuth's token endpoint.
- MojoAuth verifies the credentials and returns an Access Token.
- Your service uses the Access Token to call protected APIs.
There is no user interaction; this is a server-to-server authentication flow.
3. Implementation Steps
Step 1: Create M2M Application in MojoAuth
- Log in to the MojoAuth Dashboard.
- Configure a Machine-to-Machine application.
- Retrieve Client ID and Client Secret.
- Note the Token Endpoint (e.g.,
).https://your-app.mojoauth.com/oauth2/token - Configure the API/Audience the M2M app is authorized to access.
Step 2: Implement Token Retrieval
Node.js
// m2m-auth.js const fetch = require('node-fetch'); async function getM2MToken() { const response = await fetch('https://your-app.mojoauth.com/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.MOJOAUTH_CLIENT_ID, client_secret: process.env.MOJOAUTH_CLIENT_SECRET, audience: process.env.MOJOAUTH_API_AUDIENCE, // optional scope: 'read:data write:data', // requested scopes }), }); if (!response.ok) { throw new Error(`Token request failed: ${response.statusText}`); } const data = await response.json(); console.log('Access Token:', data.access_token); console.log('Expires In:', data.expires_in, 'seconds'); return data.access_token; } // Usage: call a protected API async function callProtectedAPI() { const token = await getM2MToken(); const response = await fetch('https://api.example.com/data', { headers: { Authorization: `Bearer ${token}` }, }); const data = await response.json(); console.log('API Response:', data); } callProtectedAPI();
Python
# m2m_auth.py import os import requests def get_m2m_token(): response = requests.post( 'https://your-app.mojoauth.com/oauth2/token', data={ 'grant_type': 'client_credentials', 'client_id': os.getenv('MOJOAUTH_CLIENT_ID'), 'client_secret': os.getenv('MOJOAUTH_CLIENT_SECRET'), 'audience': os.getenv('MOJOAUTH_API_AUDIENCE', ''), 'scope': 'read:data write:data', }, ) response.raise_for_status() data = response.json() print(f"Access Token: {data['access_token']}") print(f"Expires In: {data['expires_in']} seconds") return data['access_token'] def call_protected_api(): token = get_m2m_token() response = requests.get( 'https://api.example.com/data', headers={'Authorization': f'Bearer {token}'}, ) print('API Response:', response.json()) if __name__ == '__main__': call_protected_api()
Go
// m2m_auth.go package main import ( "context" "fmt" "log" "os" "golang.org/x/oauth2" "golang.org/x/oauth2/clientcredentials" ) func main() { config := &clientcredentials.Config{ ClientID: os.Getenv("MOJOAUTH_CLIENT_ID"), ClientSecret: os.Getenv("MOJOAUTH_CLIENT_SECRET"), TokenURL: "https://your-app.mojoauth.com/oauth2/token", Scopes: []string{"read:data", "write:data"}, } token, err := config.Token(context.Background()) if err != nil { log.Fatal("Token request failed:", err) } fmt.Println("Access Token:", token.AccessToken) fmt.Println("Expires:", token.Expiry) // Use token to call a protected API client := config.Client(context.Background()) resp, err := client.Get("https://api.example.com/data") if err != nil { log.Fatal("API call failed:", err) } defer resp.Body.Close() fmt.Println("API Response Status:", resp.Status) }
C# / .NET
// M2MAuth.cs using System.Net.Http.Headers; var client = new HttpClient(); var tokenRequest = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("grant_type", "client_credentials"), new KeyValuePair<string, string>("client_id", Environment.GetEnvironmentVariable("MOJOAUTH_CLIENT_ID")!), new KeyValuePair<string, string>("client_secret", Environment.GetEnvironmentVariable("MOJOAUTH_CLIENT_SECRET")!), new KeyValuePair<string, string>("scope", "read:data write:data"), }); var tokenResponse = await client.PostAsync("https://your-app.mojoauth.com/oauth2/token", tokenRequest); tokenResponse.EnsureSuccessStatusCode(); var tokenData = await tokenResponse.Content.ReadFromJsonAsync<TokenResponse>(); Console.WriteLine($"Access Token: {tokenData!.AccessToken}"); // Call a protected API client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenData.AccessToken); var apiResponse = await client.GetStringAsync("https://api.example.com/data"); Console.WriteLine($"API Response: {apiResponse}"); record TokenResponse( [property: System.Text.Json.Serialization.JsonPropertyName("access_token")] string AccessToken, [property: System.Text.Json.Serialization.JsonPropertyName("expires_in")] int ExpiresIn );
cURL
# Get M2M token curl -X POST https://your-app.mojoauth.com/oauth2/token \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET" \ -d "scope=read:data write:data"
Step 3: Token Caching (Best Practice)
Tokens should be cached and reused until they expire. Here is a Node.js example:
// m2m-auth-cached.js let cachedToken = null; let tokenExpiry = 0; async function getM2MToken() { // Return cached token if still valid (with 60s buffer) if (cachedToken && Date.now() < tokenExpiry - 60000) { return cachedToken; } const response = await fetch('https://your-app.mojoauth.com/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'client_credentials', client_id: process.env.MOJOAUTH_CLIENT_ID, client_secret: process.env.MOJOAUTH_CLIENT_SECRET, scope: 'read:data write:data', }), }); const data = await response.json(); cachedToken = data.access_token; tokenExpiry = Date.now() + data.expires_in * 1000; return cachedToken; }
Step 4: Test the Connection
- Set environment variables:
andMOJOAUTH_CLIENT_ID
.MOJOAUTH_CLIENT_SECRET - Run the script for your language.
- Verify that an Access Token is returned.
- Verify that the protected API responds successfully with the token.
4. Additional Considerations
- Token Caching: Always cache tokens and reuse them until expiry. Requesting a new token for every API call is inefficient and may trigger rate limits.
- Security: Store Client Secrets securely (environment variables, vault, secrets manager). Never hard-code them.
- Scopes: Request only the scopes your service actually needs (principle of least privilege).
- Error Handling: Handle token request failures, expired tokens, and API-level authorization errors.
- Rate Limiting: Be aware of MojoAuth's rate limits on the token endpoint.
5. Support
- MojoAuth Documentation: mojoauth.com/docs
- OAuth2 Spec: Refer to RFC 6749 Section 4.4 for the Client Credentials grant specification.