authentication and credentials for agentic workflows

πŸ” Authentication and Credentials for Agentic Workflows

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "authentication and credentials for agentic workflows" with this command: npx skills add hack23/riksdagsmonitor/hack23-riksdagsmonitor-authentication-and-credentials-for-agentic-workflows

πŸ” Authentication and Credentials for Agentic Workflows

πŸ“‹ Overview

This skill provides comprehensive security patterns for managing authentication and credentials in GitHub Agentic Workflows. It covers GitHub token types, secure credential storage, token rotation strategies, least privilege access control, MCP server authentication, and API key management best practices for production-ready autonomous agent systems.

🎯 Core Concepts

Authentication Architecture

graph TB subgraph "Credential Sources" A[GitHub Secrets] --> B[Environment Variables] C[Vault] --> B D[AWS Secrets Manager] --> B end

subgraph "Agent Runtime"
    B --> E[Credential Manager]
    E --> F{Token Type}
    F -->|GITHUB_TOKEN| G[GitHub API]
    F -->|PAT| H[Extended Permissions]
    F -->|GitHub App| I[Installation Token]
    F -->|API Keys| J[External Services]
end

subgraph "MCP Servers"
    E --> K[MCP Authentication]
    K --> L[GitHub MCP]
    K --> M[Custom MCP]
    K --> N[Third-Party MCP]
end

subgraph "Security Controls"
    E --> O[Least Privilege]
    E --> P[Token Rotation]
    E --> Q[Audit Logging]
end

style E fill:#00d9ff
style O fill:#ff006e
style P fill:#ffbe0b

Security Principles

  • Least Privilege: Minimal permissions required for task

  • Defense in Depth: Multiple layers of security

  • Token Rotation: Regular credential updates

  • Audit Trail: Complete authentication logging

  • Secure Storage: Encrypted credential management

  • Time-Limited Access: Short-lived tokens when possible

πŸ”‘ GitHub Token Types

  1. GITHUB_TOKEN (Automatic)

Characteristics

Automatic token provided by GitHub Actions

- Automatically created per workflow run

- Expires when workflow completes

- Scoped to the repository

- Configurable permissions

permissions: contents: read # Read repository contents pull-requests: write # Create/update PRs issues: write # Create/update issues statuses: read # Read commit statuses

Usage Pattern

.github/workflows/agent-pr-review.yml

name: Agent PR Review

on: pull_request: types: [opened, synchronize]

permissions: contents: read pull-requests: write

jobs: review: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 with: token: ${{ secrets.GITHUB_TOKEN }} # Automatic token

  - name: Run Agent Review
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    run: |
      node scripts/agents/pr-reviewer.js \
        --pr-number=${{ github.event.pull_request.number }}
        
  - name: Post Review Comment
    uses: actions/github-script@v7
    with:
      github-token: ${{ secrets.GITHUB_TOKEN }}
      script: |
        await github.rest.pulls.createReview({
          owner: context.repo.owner,
          repo: context.repo.repo,
          pull_number: context.issue.number,
          body: 'Agent review completed βœ…',
          event: 'COMMENT'
        });

Limitations

GITHUB_TOKEN limitations:

❌ Cannot trigger other workflow runs

❌ Cannot access other repositories

❌ Cannot push to protected branches (unless configured)

❌ Expires at end of workflow run

❌ Limited to repository permissions

Use PAT for these scenarios:

βœ… Triggering workflows

βœ… Cross-repository operations

βœ… Protected branch pushes

βœ… Long-running agent processes

  1. Personal Access Token (PAT)

Classic PAT Configuration

GitHub Settings β†’ Developer settings β†’ Personal access tokens β†’ Tokens (classic)

Recommended Scopes for Agentic Workflows:

βœ… repo (Full control of private repositories)

- repo:status (Access commit status)

- repo_deployment (Access deployment status)

- public_repo (Access public repositories)

- repo:invite (Access repository invitations)

βœ… workflow (Update GitHub Action workflows)

βœ… write:packages (Upload packages to GitHub Package Registry)

βœ… read:packages (Download packages from GitHub Package Registry)

βœ… admin:org (Full control of organizations)

- read:org (Read organization data)

βœ… admin:repo_hook (Full control of repository hooks)

Expiration: 90 days (recommended for security)

Fine-Grained PAT (Recommended)

GitHub Settings β†’ Developer settings β†’ Personal access tokens β†’ Fine-grained tokens

Permissions for Agentic Workflows:

Repository permissions:

- Actions: Read and write

- Contents: Read and write

- Issues: Read and write

- Metadata: Read-only (mandatory)

- Pull requests: Read and write

- Workflows: Read and write

Organization permissions (if needed):

- Members: Read-only

Account permissions:

- None (unless required)

Repository access:

- Only select repositories (principle of least privilege)

Expiration: 90 days

Usage in Workflows

Store PAT as repository secret:

Repository Settings β†’ Secrets and variables β†’ Actions β†’ New repository secret

Name: COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN

jobs: agent-task: runs-on: ubuntu-latest steps: - name: Checkout with PAT uses: actions/checkout@v4 with: token: ${{ secrets.COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN }} fetch-depth: 0

  - name: Run Agent with PAT
    env:
      GITHUB_TOKEN: ${{ secrets.COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN }}
    run: |
      # Agent can now:
      # - Push to protected branches
      # - Trigger other workflows
      # - Access multiple repositories
      node scripts/agents/cross-repo-agent.js
      
  - name: Create PR (Triggers Workflows)
    uses: peter-evans/create-pull-request@v6
    with:
      token: ${{ secrets.COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN }}
      title: 'Agent Update'
      body: 'Automated changes by agent'
      branch: 'agent/update'

3. GitHub App Token

App Creation and Configuration

1. Create GitHub App

GitHub Settings β†’ Developer settings β†’ GitHub Apps β†’ New GitHub App

App Configuration:

- Name: Agentic Workflow Bot

- Homepage URL: https://github.com/your-org/your-repo

- Webhook: Optional (for event-driven agents)

- Permissions:

Repository permissions:

- Actions: Read & write

- Contents: Read & write

- Issues: Read & write

- Pull requests: Read & write

- Workflows: Read & write

Organization permissions:

- Members: Read-only (if needed)

2. Generate Private Key

Download private key (PEM file)

3. Install App on Repository

Install App β†’ Select repositories

4. Store App Credentials

- APP_ID: From app settings

- PRIVATE_KEY: PEM file content (as secret)

- INSTALLATION_ID: From installation URL

Generate Installation Token

// scripts/agents/lib/github-app-auth.js import { createAppAuth } from '@octokit/auth-app'; import { Octokit } from '@octokit/rest'; import fs from 'fs';

/**

  • GitHub App authentication for agentic workflows */ class GitHubAppAuth { constructor(options = {}) { this.appId = options.appId || process.env.GITHUB_APP_ID; this.privateKey = options.privateKey || process.env.GITHUB_APP_PRIVATE_KEY; this.installationId = options.installationId || process.env.GITHUB_APP_INSTALLATION_ID;

    if (!this.appId || !this.privateKey) { throw new Error('GitHub App credentials not configured'); } }

/**

  • Create authenticated Octokit instance */ async createOctokit() { const auth = createAppAuth({ appId: this.appId, privateKey: this.privateKey, installationId: this.installationId });
const { token } = await auth({ type: 'installation' });
return new Octokit({
  auth: token,
  userAgent: 'agentic-workflow-bot/1.0.0'
});

}

/**

  • Get installation token (for direct use) */ async getInstallationToken() { const auth = createAppAuth({ appId: this.appId, privateKey: this.privateKey, installationId: this.installationId });
const { token, expiresAt, permissions } = await auth({ type: 'installation' });
return {
  token,
  expiresAt: new Date(expiresAt),
  permissions
};

}

/**

  • Revoke installation token (cleanup) */ async revokeToken(token) { const octokit = new Octokit({ auth: token }); await octokit.apps.deleteToken({ client_id: this.appId, access_token: token }); } }

export default GitHubAppAuth;

// Usage in workflow const appAuth = new GitHubAppAuth({ appId: process.env.GITHUB_APP_ID, privateKey: process.env.GITHUB_APP_PRIVATE_KEY, installationId: process.env.GITHUB_APP_INSTALLATION_ID });

const octokit = await appAuth.createOctokit();

// Use octokit for API calls const { data: pr } = await octokit.pulls.get({ owner: 'owner', repo: 'repo', pull_number: 123 });

Workflow Integration

.github/workflows/agent-github-app.yml

name: Agent with GitHub App

on: issues: types: [opened]

jobs: process-issue: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4

  - name: Generate App Token
    id: app-token
    uses: actions/create-github-app-token@v1
    with:
      app-id: ${{ secrets.GITHUB_APP_ID }}
      private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }}
      
  - name: Run Agent with App Token
    env:
      GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
    run: |
      node scripts/agents/issue-processor.js \
        --issue-number=${{ github.event.issue.number }}
        
  - name: Cleanup (token automatically revoked)
    run: echo "Token expires in 1 hour"

πŸ”’ Credential Storage

  1. GitHub Secrets

Repository Secrets

Add secret via GitHub UI:

Repository β†’ Settings β†’ Secrets and variables β†’ Actions β†’ New repository secret

Or via GitHub CLI:

gh secret set ANTHROPIC_API_KEY --body "sk-ant-..." gh secret set OPENAI_API_KEY --body "sk-..." gh secret set MCP_DATABASE_URL --body "postgresql://..."

List secrets:

gh secret list

Delete secret:

gh secret delete ANTHROPIC_API_KEY

Environment Secrets

Use GitHub Environments for stage-specific secrets

Repository β†’ Settings β†’ Environments β†’ New environment

Development environment

name: development secrets:

  • API_KEY: dev-key-123
  • DATABASE_URL: postgresql://dev-db

Production environment

name: production secrets:

  • API_KEY: prod-key-456
  • DATABASE_URL: postgresql://prod-db protection_rules:
  • required_reviewers: 2
  • wait_timer: 5 # minutes

Use environment secrets in workflow

jobs: deploy-dev: runs-on: ubuntu-latest environment: development steps: - name: Deploy Agent env: API_KEY: ${{ secrets.API_KEY }} DATABASE_URL: ${{ secrets.DATABASE_URL }} run: ./deploy.sh

deploy-prod: runs-on: ubuntu-latest environment: production needs: deploy-dev steps: - name: Deploy Agent env: API_KEY: ${{ secrets.API_KEY }} DATABASE_URL: ${{ secrets.DATABASE_URL }} run: ./deploy.sh

Organization Secrets

For multi-repository access

Organization β†’ Settings β†’ Secrets and variables β†’ Actions

Add organization secret:

gh secret set SHARED_API_KEY
--org your-org
--repos "repo1,repo2,repo3"
--body "shared-key-789"

  1. External Secret Managers

AWS Secrets Manager

.github/workflows/agent-aws-secrets.yml

name: Agent with AWS Secrets

jobs: agent-task: runs-on: ubuntu-latest permissions: id-token: write # For OIDC authentication contents: read

steps:
  - name: Configure AWS Credentials
    uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
      aws-region: us-east-1
      
  - name: Retrieve Secrets
    id: secrets
    run: |
      # Get secret from AWS Secrets Manager
      SECRET_JSON=$(aws secretsmanager get-secret-value \
        --secret-id agentic-workflow/production \
        --query SecretString \
        --output text)
      
      # Parse and export secrets
      echo "::add-mask::$(echo $SECRET_JSON | jq -r '.api_key')"
      echo "API_KEY=$(echo $SECRET_JSON | jq -r '.api_key')" >> $GITHUB_ENV
      
  - name: Run Agent with Secret
    env:
      API_KEY: ${{ env.API_KEY }}
    run: node scripts/agents/secure-agent.js

HashiCorp Vault

.github/workflows/agent-vault.yml

name: Agent with Vault

jobs: agent-task: runs-on: ubuntu-latest steps: - name: Retrieve Secrets from Vault id: secrets uses: hashicorp/vault-action@v2 with: url: https://vault.example.com method: approle roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} secrets: | secret/data/agentic-workflow api_key | API_KEY ; secret/data/agentic-workflow db_password | DB_PASSWORD

  - name: Run Agent
    env:
      API_KEY: ${{ steps.secrets.outputs.API_KEY }}
      DB_PASSWORD: ${{ steps.secrets.outputs.DB_PASSWORD }}
    run: node scripts/agents/vault-agent.js

3. Secure Secret Handling

// scripts/agents/lib/credential-manager.js import { SecretsManager } from '@aws-sdk/client-secrets-manager'; import crypto from 'crypto';

/**

  • Secure credential manager for agentic workflows */ class CredentialManager { constructor(options = {}) { this.secretsClient = options.secretsClient || new SecretsManager({ region: process.env.AWS_REGION || 'us-east-1' }); this.cache = new Map(); this.cacheTTL = options.cacheTTL || 300000; // 5 minutes }

/**

  • Get secret with caching */ async getSecret(secretName, options = {}) { // Check cache const cached = this.cache.get(secretName); if (cached && Date.now() - cached.timestamp < this.cacheTTL) { return cached.value; }
// Retrieve from secrets manager
let secret;
if (secretName.startsWith('aws:')) {
  secret = await this.getAWSSecret(secretName.replace('aws:', ''));
} else if (secretName.startsWith('env:')) {
  secret = process.env[secretName.replace('env:', '')];
} else {
  throw new Error(`Unknown secret source for: ${secretName}`);
}

// Cache secret
this.cache.set(secretName, {
  value: secret,
  timestamp: Date.now()
});

return secret;

}

/**

  • Get secret from AWS Secrets Manager */ async getAWSSecret(secretId) { const response = await this.secretsClient.getSecretValue({ SecretId: secretId });
if (response.SecretString) {
  return JSON.parse(response.SecretString);
} else {
  throw new Error(`Secret ${secretId} is not a string`);
}

}

/**

  • Rotate secret */ async rotateSecret(secretName, newValue) { if (secretName.startsWith('aws:')) { await this.rotateAWSSecret(secretName.replace('aws:', ''), newValue); }
// Invalidate cache
this.cache.delete(secretName);

}

/**

  • Rotate AWS secret */ async rotateAWSSecret(secretId, newValue) { await this.secretsClient.updateSecret({ SecretId: secretId, SecretString: JSON.stringify(newValue) }); }

/**

  • Mask sensitive values in logs / maskSecret(value) { if (!value || value.length < 8) return '**';
// Show first and last 4 characters
return `${value.slice(0, 4)}...${value.slice(-4)}`;

}

/**

  • Validate secret format */ validateSecret(secret, type) { switch (type) { case 'github-token': // GitHub tokens start with specific prefixes return /^(ghp_|gho_|ghu_|ghs_|ghr_)/.test(secret);

    case 'anthropic-key': return /^sk-ant-api03-/.test(secret);

    case 'openai-key': return /^sk-/.test(secret);

    default: return true; } }

/**

  • Clear cache (on shutdown) */ clearCache() { this.cache.clear(); } }

export default CredentialManager;

// Usage const credManager = new CredentialManager();

// Get secret const apiKey = await credManager.getSecret('aws:agentic-workflow/api-key');

// Mask in logs console.log(Using API key: ${credManager.maskSecret(apiKey)});

// Validate if (!credManager.validateSecret(apiKey, 'anthropic-key')) { throw new Error('Invalid API key format'); }

πŸ”„ Token Rotation

  1. Automated Rotation Strategy

.github/workflows/rotate-secrets.yml

name: Rotate Secrets

on: schedule: - cron: '0 0 1 * *' # Monthly rotation workflow_dispatch:

permissions: contents: read issues: write

jobs: check-expiration: name: Check Token Expiration runs-on: ubuntu-latest outputs: needs_rotation: ${{ steps.check.outputs.needs_rotation }}

steps:
  - name: Check PAT Expiration
    id: check
    env:
      GITHUB_TOKEN: ${{ secrets.COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN }}
    run: |
      # Get token expiration
      EXPIRATION=$(gh api /user \
        -H "Accept: application/vnd.github+json" \
        | jq -r '.token_expires_at // "never"')
      
      if [ "$EXPIRATION" = "never" ]; then
        echo "⚠️ Token has no expiration (not recommended)"
        echo "needs_rotation=false" >> $GITHUB_OUTPUT
        exit 0
      fi
      
      # Calculate days until expiration
      EXPIRE_DATE=$(date -d "$EXPIRATION" +%s)
      NOW=$(date +%s)
      DAYS_LEFT=$(( ($EXPIRE_DATE - $NOW) / 86400 ))
      
      echo "Days until expiration: $DAYS_LEFT"
      
      if [ $DAYS_LEFT -lt 14 ]; then
        echo "⚠️ Token expires in $DAYS_LEFT days - rotation needed"
        echo "needs_rotation=true" >> $GITHUB_OUTPUT
      else
        echo "βœ… Token valid for $DAYS_LEFT days"
        echo "needs_rotation=false" >> $GITHUB_OUTPUT
      fi
      

notify-rotation: name: Notify About Rotation needs: check-expiration if: needs.check-expiration.outputs.needs_rotation == 'true' runs-on: ubuntu-latest

steps:
  - name: Create Rotation Issue
    uses: actions/github-script@v7
    with:
      github-token: ${{ secrets.GITHUB_TOKEN }}
      script: |
        // Check if rotation issue already exists
        const { data: issues } = await github.rest.issues.listForRepo({
          owner: context.repo.owner,
          repo: context.repo.repo,
          state: 'open',
          labels: 'security,credential-rotation',
          per_page: 10
        });
        
        const existingIssue = issues.find(issue => 
          issue.title.includes('Token Rotation Required')
        );
        
        if (existingIssue) {
          console.log('Rotation issue already exists:', existingIssue.number);
          return;
        }
        
        // Create new rotation issue
        await github.rest.issues.create({
          owner: context.repo.owner,
          repo: context.repo.repo,
          title: 'πŸ” Token Rotation Required',
          body: `## Security Notice
          

The GitHub Personal Access Token used by agentic workflows is expiring soon.

Action Required

  1. Go to GitHub Settings β†’ Personal Access Tokens
  2. Generate a new fine-grained token with same permissions
  3. Update repository secret: `COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN`
  4. Test workflows after rotation
  5. Close this issue

Token Details

  • Current Status: Expires in < 14 days
  • Used In: Agentic workflow automations
  • Permissions: Contents, PRs, Issues, Workflows

Auto-generated rotation reminder`, labels: ['security', 'credential-rotation', 'high-priority'], assignees: ['security-team'] });

  1. Zero-Downtime Rotation

// scripts/agents/lib/rotation-handler.js

/**

  • Handle token rotation without downtime */ class RotationHandler { constructor(options = {}) { this.primaryToken = options.primaryToken; this.fallbackToken = options.fallbackToken; this.rotationState = 'active'; // active, rotating, completed }

/**

  • Get active token (with fallback) */ async getToken() { try { // Try primary token await this.validateToken(this.primaryToken); return this.primaryToken; } catch (error) { if (this.fallbackToken) { console.warn('Primary token failed, using fallback'); return this.fallbackToken; } throw error; } }

/**

  • Validate token is still valid */ async validateToken(token) { const response = await fetch('https://api.github.com/user', { headers: { 'Authorization': Bearer ${token}, 'Accept': 'application/vnd.github+json' } });
if (!response.ok) {
  throw new Error('Token validation failed');
}

return true;

}

/**

  • Perform rotation */ async rotate(newToken) { this.rotationState = 'rotating';
try {
  // Validate new token
  await this.validateToken(newToken);
  
  // Set new token as fallback first
  this.fallbackToken = newToken;
  
  // Wait for in-flight operations to complete
  await this.waitForInFlightOperations();
  
  // Switch tokens
  this.primaryToken = newToken;
  this.fallbackToken = null;
  
  this.rotationState = 'completed';
  console.log('βœ… Token rotation completed successfully');
} catch (error) {
  this.rotationState = 'failed';
  console.error('❌ Token rotation failed:', error);
  throw error;
}

}

async waitForInFlightOperations() { // Wait for pending operations await new Promise(resolve => setTimeout(resolve, 5000)); } }

export default RotationHandler;

πŸ›‘οΈ Least Privilege Access

  1. Granular Permissions

Minimal permissions for different agent types

Read-only analysis agent

permissions: contents: read pull-requests: read

PR review agent

permissions: contents: read pull-requests: write

Issue triage agent

permissions: contents: read issues: write

Deployment agent

permissions: contents: read deployments: write statuses: write

Full automation agent (use sparingly)

permissions: contents: write pull-requests: write issues: write workflows: write

  1. Permission Validation

// scripts/agents/lib/permission-checker.js

/**

  • Validate agent has required permissions */ class PermissionChecker { constructor(octokit) { this.octokit = octokit; this.permissions = null; }

/**

  • Get current token permissions */ async getPermissions() { if (this.permissions) return this.permissions;
const { data } = await this.octokit.rest.users.getAuthenticated();
this.permissions = data.permissions || {};

return this.permissions;

}

/**

  • Check if permission is granted */ async hasPermission(permission, level = 'read') { const perms = await this.getPermissions(); const currentLevel = perms[permission];
if (!currentLevel) return false;
const levels = ['read', 'write', 'admin'];
const currentIndex = levels.indexOf(currentLevel);
const requiredIndex = levels.indexOf(level);

return currentIndex >= requiredIndex;

}

/**

  • Require specific permission (throw if not granted) */ async requirePermission(permission, level = 'read') { const has = await this.hasPermission(permission, level);
if (!has) {
  throw new Error(
    `Missing required permission: ${permission}:${level}. ` +
    `Please update workflow permissions.`
  );
}

}

/**

  • Check multiple permissions */ async checkPermissions(required) { const results = {};
for (const [permission, level] of Object.entries(required)) {
  results[permission] = await this.hasPermission(permission, level);
}

return results;

} }

// Usage const checker = new PermissionChecker(octokit);

// Check single permission await checker.requirePermission('pull_requests', 'write');

// Check multiple permissions const perms = await checker.checkPermissions({ contents: 'read', pull_requests: 'write', issues: 'write' });

if (!perms.pull_requests) { console.error('Missing required PR write permission'); }

πŸ”Œ MCP Server Authentication

  1. GitHub MCP Server

// .github/copilot-mcp.json { "mcpServers": { "github": { "type": "local", "command": "npx", "args": [ "-y", "@modelcontextprotocol/server-github", "--toolsets", "all", "--tools", "" ], "env": { "GITHUB_TOKEN": "${{ secrets.COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN }}", "GITHUB_PERSONAL_ACCESS_TOKEN": "${{ secrets.COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN }}", "GITHUB_OWNER": "Hack23", "GITHUB_API_URL": "https://api.githubcopilot.com/mcp/insiders" }, "tools": [""] } } }

  1. Custom MCP Server with Auth

// scripts/mcp-servers/authenticated-mcp.js import { McpServer } from '@modelcontextprotocol/sdk'; import { createHmac, timingSafeEqual } from 'crypto';

/**

  • Custom MCP server with authentication */ class AuthenticatedMCPServer extends McpServer { constructor(options = {}) { super(options);

    this.apiKey = options.apiKey || process.env.MCP_API_KEY; this.allowedOrigins = options.allowedOrigins || ['localhost'];

    if (!this.apiKey) { throw new Error('MCP_API_KEY not configured'); }

    // Add authentication middleware this.use(this.authenticateRequest.bind(this)); }

/**

  • Authenticate incoming requests */ async authenticateRequest(req, res, next) { const authHeader = req.headers['authorization'];
if (!authHeader) {
  return res.status(401).json({
    error: 'Missing Authorization header'
  });
}

const [type, credentials] = authHeader.split(' ');

if (type === 'Bearer') {
  // API key authentication
  if (!this.validateAPIKey(credentials)) {
    return res.status(401).json({
      error: 'Invalid API key'
    });
  }
} else if (type === 'HMAC') {
  // HMAC signature authentication
  if (!this.validateHMACSignature(req, credentials)) {
    return res.status(401).json({
      error: 'Invalid HMAC signature'
    });
  }
} else {
  return res.status(401).json({
    error: 'Unsupported authentication type'
  });
}

next();

}

/**

  • Validate API key */ validateAPIKey(providedKey) { // Use timing-safe comparison const expected = Buffer.from(this.apiKey); const provided = Buffer.from(providedKey);
if (expected.length !== provided.length) {
  return false;
}

return timingSafeEqual(expected, provided);

}

/**

  • Validate HMAC signature */ validateHMACSignature(req, signature) { const payload = JSON.stringify(req.body); const hmac = createHmac('sha256', this.apiKey); hmac.update(payload); const expected = hmac.digest('hex');
return timingSafeEqual(
  Buffer.from(expected),
  Buffer.from(signature)
);

}

/**

  • Generate HMAC signature for client */ static generateSignature(payload, apiKey) { const hmac = createHmac('sha256', apiKey); hmac.update(JSON.stringify(payload)); return hmac.digest('hex'); } }

export default AuthenticatedMCPServer;

  1. MCP Client Authentication

// scripts/agents/lib/mcp-client-auth.js

/**

  • MCP client with authentication */ class AuthenticatedMCPClient { constructor(options = {}) { this.gatewayUrl = options.gatewayUrl || 'http://localhost:3000'; this.apiKey = options.apiKey || process.env.MCP_API_KEY; this.authType = options.authType || 'bearer'; // bearer or hmac }

/**

  • Call MCP tool with authentication */ async callTool(toolName, params) { const headers = this.getAuthHeaders(params);
const response = await fetch(`${this.gatewayUrl}/tools/${toolName}`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    ...headers
  },
  body: JSON.stringify(params)
});

if (!response.ok) {
  throw new Error(`MCP call failed: ${response.statusText}`);
}

return response.json();

}

/**

  • Get authentication headers */ getAuthHeaders(params) { if (this.authType === 'bearer') { return { 'Authorization': Bearer ${this.apiKey} }; } else if (this.authType === 'hmac') { const signature = AuthenticatedMCPServer.generateSignature(params, this.apiKey); return { 'Authorization': HMAC ${signature} }; }
throw new Error(`Unknown auth type: ${this.authType}`);

} }

export default AuthenticatedMCPClient;

πŸ”‘ API Key Management

  1. Multi-Provider Key Management

// scripts/agents/lib/api-key-manager.js

/**

  • Manage API keys for multiple providers */ class APIKeyManager { constructor() { this.keys = { anthropic: process.env.ANTHROPIC_API_KEY, openai: process.env.OPENAI_API_KEY, github: process.env.GITHUB_TOKEN, riksdag: process.env.RIKSDAG_API_TOKEN };

    this.usage = new Map(); this.rateLimits = { anthropic: { requests: 50, period: 60000 }, // 50/min openai: { requests: 60, period: 60000 }, // 60/min github: { requests: 5000, period: 3600000 } // 5000/hour }; }

/**

  • Get API key for provider */ getKey(provider) { const key = this.keys[provider];
if (!key) {
  throw new Error(`API key not configured for provider: ${provider}`);
}

return key;

}

/**

  • Check rate limit before API call */ async checkRateLimit(provider) { const limit = this.rateLimits[provider]; if (!limit) return true;
const usage = this.usage.get(provider) || { count: 0, resetAt: Date.now() + limit.period };
// Reset if period expired
if (Date.now() >= usage.resetAt) {
  usage.count = 0;
  usage.resetAt = Date.now() + limit.period;
}

// Check limit
if (usage.count >= limit.requests) {
  const waitTime = usage.resetAt - Date.now();
  throw new Error(
    `Rate limit exceeded for ${provider}. ` +
    `Wait ${Math.ceil(waitTime / 1000)}s before next request.`
  );
}

// Increment usage
usage.count++;
this.usage.set(provider, usage);

return true;

}

/**

  • Make authenticated API call */ async callAPI(provider, url, options = {}) { await this.checkRateLimit(provider);
const key = this.getKey(provider);
const headers = this.getAuthHeaders(provider, key);

const response = await fetch(url, {
  ...options,
  headers: {
    ...headers,
    ...options.headers
  }
});

return response;

}

/**

  • Get auth headers for provider */ getAuthHeaders(provider, key) { switch (provider) { case 'anthropic': return { 'x-api-key': key, 'anthropic-version': '2023-06-01' };

    case 'openai': return { 'Authorization': Bearer ${key} };

    case 'github': return { 'Authorization': Bearer ${key}, 'Accept': 'application/vnd.github+json', 'X-GitHub-Api-Version': '2022-11-28' };

    default: return { 'Authorization': Bearer ${key} }; } } }

export default APIKeyManager;

πŸ”’ Security Best Practices

  1. Secret Scanning

.github/workflows/secret-scan.yml

name: Secret Scanning

on: push: branches: [main, develop] pull_request:

jobs: scan: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v4 with: fetch-depth: 0

  - name: TruffleHog Secret Scan
    uses: trufflesecurity/trufflehog@main
    with:
      path: ./
      base: ${{ github.event.repository.default_branch }}
      head: HEAD
      extra_args: --debug --only-verified

2. Credential Auditing

// scripts/security/audit-credentials.js

/**

  • Audit credential usage */ class CredentialAuditor { constructor() { this.auditLog = []; }

/**

  • Log credential access */ logAccess(credentialName, operation, metadata = {}) { const entry = { timestamp: new Date().toISOString(), credential: credentialName, operation, agent_id: process.env.AGENT_ID, session_id: process.env.GITHUB_RUN_ID, ...metadata };
this.auditLog.push(entry);
// Log to stdout (captured by workflow)
console.log(JSON.stringify({
  event_type: 'credential_access',
  ...entry
}));

}

/**

  • Export audit log */ exportLog() { return { audit_period: { start: this.auditLog[0]?.timestamp, end: this.auditLog[this.auditLog.length - 1]?.timestamp }, total_accesses: this.auditLog.length, unique_credentials: new Set(this.auditLog.map(e => e.credential)).size, entries: this.auditLog }; } }

export default CredentialAuditor;

πŸ“š Related Skills

  • gh-aw-github-actions-integration - CI/CD integration

  • gh-aw-logging-monitoring - Observability patterns

  • gh-aw-mcp-gateway - MCP Gateway setup

  • gh-aw-safe-outputs - Safe output handling

  • gh-aw-containerization - Container security

πŸ”— References

GitHub Documentation

  • GitHub Tokens

  • GitHub Apps

  • Encrypted Secrets

  • OIDC with AWS

Security Best Practices

  • OWASP Secrets Management

  • CIS AWS Secrets Manager Benchmark

βœ… Remember Checklist

When managing authentication and credentials:

  • Use fine-grained PATs over classic PATs

  • Set token expiration (90 days maximum)

  • Apply least privilege permissions

  • Store secrets in GitHub Secrets or external vault

  • Never commit credentials to repository

  • Implement token rotation strategy

  • Audit credential access

  • Use environment-specific secrets

  • Validate token permissions before use

  • Mask secrets in logs

  • Enable secret scanning

  • Use short-lived tokens when possible

  • Implement fallback authentication

  • Monitor token expiration

  • Document credential requirements

License: Apache-2.0

Version: 1.0.0

Last Updated: 2026-02-17

Maintained by: Hack23 Organization

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

copilot-agent-patterns

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

logging and monitoring for agentic workflows

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

gh-aw-workflow-authoring

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

containerization for agentic workflows

No summary provided by upstream source.

Repository SourceNeeds Review