| name | Authentication and Credentials for Agentic Workflows |
| description | Comprehensive security patterns for managing authentication in GitHub Agentic Workflows including GitHub token types, credential storage, token rotation, least privilege access control, MCP server authentication, and API key management best practices. |
| license | Apache-2.0 |
| version | 2.0.0 |
| last_updated | 2026-04-02 |
| tags | ["authentication","credentials","security","agentic-workflows","github-tokens","api-keys","secrets-management","least-privilege","token-rotation","mcp-authentication"] |
🔐 Authentication and Credentials for Agentic Workflows
🔴 AI FIRST Quality Principle
Apply the AI FIRST principle: never accept first-pass quality. Minimum 2 iterations. Read all output, improve every section. No shortcuts.
📋 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
permissions:
contents: read
pull-requests: write
issues: write
statuses: read
Usage Pattern
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 }}
- 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:
Limitations
2. Personal Access Token (PAT)
Classic PAT Configuration
Fine-Grained PAT (Recommended)
Usage in Workflows
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 }}
3. GitHub App Token
App Creation and Configuration
Generate Installation Token
import { createAppAuth } from '@octokit/auth-app';
import { Octokit } from '@octokit/rest';
import fs from 'fs';
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');
}
}
async createOctokit() {
const auth = createAppAuth({
appId: this.,
: .,
: .
});
{ token } = ({ : });
({
: token,
:
});
}
() {
auth = ({
: .,
: .,
: .
});
{ token, expiresAt, permissions } = ({ : });
{
token,
: (expiresAt),
permissions
};
}
() {
octokit = ({ : token });
octokit..({
: .,
: token
});
}
}
;
appAuth = ({
: process..,
: process..,
: process..
});
octokit = appAuth.();
{ : pr } = octokit..({
: ,
: ,
:
});
Workflow Integration
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 }}
🔒 Credential Storage
1. GitHub Secrets
Repository Secrets
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://..."
gh secret list
gh secret delete ANTHROPIC_API_KEY
Environment Secrets
name: development
secrets:
- API_KEY: dev-key-123
- DATABASE_URL: postgresql://dev-db
name: production
secrets:
- API_KEY: prod-key-456
- DATABASE_URL: postgresql://prod-db
protection_rules:
- required_reviewers: 2
- wait_timer: 5
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
gh secret set SHARED_API_KEY \
--org your-org \
--repos "repo1,repo2,repo3" \
--body "shared-key-789"
2. External Secret Managers
AWS Secrets Manager
name: Agent with AWS Secrets
jobs:
agent-task:
runs-on: ubuntu-latest
permissions:
id-token: write
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)
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
HashiCorp Vault
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
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
import crypto from 'crypto';
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;
}
async getSecret(secretName, options = {}) {
const cached = this.cache.get(secretName);
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
return cached.value;
}
secret;
(secretName.()) {
secret = .(secretName.(, ));
} (secretName.()) {
secret = process.[secretName.(, )];
} {
();
}
..(secretName, {
: secret,
: .()
});
secret;
}
() {
response = ..({
: secretId
});
(response.) {
.(response.);
} {
();
}
}
() {
(secretName.()) {
.(secretName.(, ), newValue);
}
..(secretName);
}
() {
..({
: secretId,
: .(newValue)
});
}
() {
(!value || value. < ) ;
;
}
() {
(type) {
:
.(secret);
:
.(secret);
:
.(secret);
:
;
}
}
() {
..();
}
}
;
credManager = ();
apiKey = credManager.();
.();
(!credManager.(apiKey, )) {
();
}
🔄 Token Rotation
1. Automated Rotation Strategy
name: Rotate Secrets
on:
schedule:
- cron: '0 0 1 * *'
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)"
[ ]
{
,
}
[ ]
[, , ]
[]
2. Zero-Downtime Rotation
class RotationHandler {
constructor(options = {}) {
this.primaryToken = options.primaryToken;
this.fallbackToken = options.fallbackToken;
this.rotationState = 'active';
}
async getToken() {
try {
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;
}
}
async validateToken(token) {
const response = (, {
: {
: ,
:
}
});
(!response.) {
();
}
;
}
() {
. = ;
{
.(newToken);
. = newToken;
.();
. = newToken;
. = ;
. = ;
.();
} (error) {
. = ;
.(, error);
error;
}
}
() {
( (resolve, ));
}
}
;
🛡️ Least Privilege Access
1. Granular Permissions
permissions:
contents: read
pull-requests: read
permissions:
contents: read
pull-requests: write
permissions:
contents: read
issues: write
permissions:
contents: read
deployments: write
statuses: write
permissions:
contents: write
pull-requests: write
issues: write
workflows: write
2. Permission Validation
class PermissionChecker {
constructor(octokit) {
this.octokit = octokit;
this.permissions = null;
}
async getPermissions() {
if (this.permissions) return this.permissions;
const { data } = await this.octokit.rest.users.getAuthenticated();
this.permissions = data.permissions || {};
return this.permissions;
}
async hasPermission(permission, level = 'read') {
const perms = await this.getPermissions();
const currentLevel = perms[permission];
if (!currentLevel) return false;
const levels = [, , ];
currentIndex = levels.(currentLevel);
requiredIndex = levels.(level);
currentIndex >= requiredIndex;
}
() {
has = .(permission, level);
(!has) {
(
+
);
}
}
() {
results = {};
( [permission, level] .(required)) {
results[permission] = .(permission, level);
}
results;
}
}
checker = (octokit);
checker.(, );
perms = checker.({
: ,
: ,
:
});
(!perms.) {
.();
}
🔌 MCP Server Authentication
1. GitHub MCP Server
{
"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":
2. Custom MCP Server with Auth
import { McpServer } from '@modelcontextprotocol/sdk';
import { createHmac, timingSafeEqual } from 'crypto';
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');
}
this.use(this.authenticateRequest.bind(this));
}
async authenticateRequest(req, res, next) {
const authHeader = req.headers['authorization'];
if (!authHeader) {
res.().({
:
});
}
[type, credentials] = authHeader.();
(type === ) {
(!.(credentials)) {
res.().({
:
});
}
} (type === ) {
(!.(req, credentials)) {
res.().({
:
});
}
} {
res.().({
:
});
}
();
}
() {
expected = .(.);
provided = .(providedKey);
(expected. !== provided.) {
;
}
(expected, provided);
}
() {
payload = .(req.);
hmac = (, .);
hmac.(payload);
expected = hmac.();
(
.(expected),
.(signature)
);
}
() {
hmac = (, apiKey);
hmac.(.(payload));
hmac.();
}
}
;
3. MCP Client 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';
}
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.();
}
() {
(. === ) {
{
:
};
} (. === ) {
signature = .(params, .);
{
:
};
}
();
}
}
;
🔑 API Key Management
1. Multi-Provider Key Management
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 },
openai: { requests: 60, period: 60000 },
github: { requests: 5000, period: 3600000 }
};
}
getKey(provider) {
const key = this.keys[provider];
if (!key) {
();
}
key;
}
() {
limit = .[provider];
(!limit) ;
usage = ..(provider) || { : , : .() + limit. };
(.() >= usage.) {
usage. = ;
usage. = .() + limit.;
}
(usage. >= limit.) {
waitTime = usage. - .();
(
+
);
}
usage.++;
..(provider, usage);
;
}
() {
.(provider);
key = .(provider);
headers = .(provider, key);
response = (url, {
...options,
: {
...headers,
...options.
}
});
response;
}
() {
(provider) {
:
{
: key,
:
};
:
{
:
};
:
{
: ,
: ,
:
};
:
{
:
};
}
}
}
;
🔒 Security Best Practices
1. Secret Scanning
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
class CredentialAuditor {
constructor() {
this.auditLog = [];
}
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);
console.log(JSON.stringify({
event_type: 'credential_access',
...entry
}));
}
exportLog() {
return {
audit_period: {
start: this.auditLog[0]?.timestamp,
end: this.auditLog[this.. - ]?.
},
: ..,
: (..( e.)).,
: .
};
}
}
;
📚 Related Skills
🔗 References
GitHub Documentation
Security Best Practices
✅ Remember Checklist
When managing authentication and credentials:
License: Apache-2.0
Version: 2.0.0
Last Updated: 2026-04-02
Maintained by: Hack23 Organization
🔗 Integration with Riksdagsmonitor agentic workflows
This gh-aw skill is applied by the 11 agentic news workflows in .github/workflows/news-*.md. Their domain contract (analysis-artifact product, gate, article contract) lives in:
Upstream gh-aw docs (v0.69.3): abridged · complete · agentic-workflows blog series · source repo · GitHub CLI manual.