Token导航 LogoToken导航

Agent Skill · 研究检索

oauth2-oidc-implementer(oauth2 oidc 实现者)

用于辅助安全审计、权限检查、凭据风险、认证流程和常见漏洞排查。它适合让 Agent 梳理敏感配置、检查依赖风险、分析鉴权逻辑或生成安全复核清单。使用时不能把工具输出直接当最终结论,涉及密钥、令牌、用户数据或生产系统时,应先确认最小权限、脱敏方式和操作边界。

oauth2-oidc-implementer 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为接触敏感数据;许可证为 MIT,第三方扫描已通过,按安装量在研究检索类的 73,675 个 Skill 里排第 26,866。

总安装 3,492周安装 150GitHub Stars 33下载 1,224核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:oauth2-oidc-implementer(oauth2 oidc 实现者)
来源仓库:https://github.com/patricio0312rev/skills
仓库路径:skills/oauth2-oidc-implementer
安装命令:
npx skills add https://github.com/patricio0312rev/skills --skill oauth2-oidc-implementer
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/patricio0312rev/skills --skill oauth2-oidc-implementer

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

用于辅助 OAuth2 OIDC 实现与安全审计,适合在 Codex、Claude、Cursor、Gemini CLI 中检查凭据风险。可协助分析鉴权逻辑、梳理配置与生成安全复核清单。通过 npx skills add 命令从指定仓库安装,需确认最小权限与操作边界。

涉及令牌或生产系统时应先脱敏,不能将工具输出直接当最终结论。oauth2-oidc-implementer 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/oauth2-oidc-implementer

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/patricio0312rev/skills --skill oauth2-oidc-imp…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要接触敏感数据,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

oauth2-oidc-implementer 怎么安装?

在终端执行 npx skills add https://github.com/patricio0312rev/skills --skill oauth2-oidc-implementer。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

oauth2-oidc-implementer 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

oauth2-oidc-implementer 需要什么权限?

权限级别是接触敏感数据。该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

oauth2-oidc-implementer 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

oauth2-oidc-implementer 用的人多吗?

公开数据是累计安装 3,492 次,近一周安装 150 次,下载 1,224 次,GitHub 33 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

oauth2-oidc-implementer 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 需要接触敏感数据,装到常用环境前要先评估影响面。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 环境里有大量密钥、凭证,不方便给第三方 Skill 读取的人。
  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
oauth2-oidc-implementer

oauth2 oidc 实现者 · 本页

3,49233接触敏感数据MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

OAuth 2.0 & OIDC Implementer

Implement secure authentication with OAuth 2.0 and OpenID Connect.

Core Workflow

  1. Choose flow: Authorization Code, PKCE, Client Credentials
  2. Configure provider: Set up OAuth/OIDC provider
  3. Implement flow: Handle redirects and tokens
  4. Secure tokens: Storage and refresh
  5. Add providers: Multiple identity providers
  6. Handle sessions: Manage authenticated state

OAuth 2.0 Flows Overview

┌─────────────────────────────────────────────────────────────┐
│                      OAuth 2.0 Flows                         │
├─────────────────────────────────────────────────────────────┤
│ Authorization Code + PKCE │ Web/Mobile apps (recommended)   │
│ Client Credentials        │ Machine-to-machine              │
│ Device Code               │ TV/IoT devices                  │
│ Implicit (deprecated)     │ Do not use                      │
└─────────────────────────────────────────────────────────────┘

Authorization Code Flow with PKCE

Server Implementation (Next.js)

// lib/auth/oauth.ts
import { randomBytes, createHash } from 'crypto';

interface OAuthConfig {
  clientId: string;
  clientSecret: string;
  authorizationUrl: string;
  tokenUrl: string;
  redirectUri: string;
  scopes: string[];
}

const config: OAuthConfig = {
  clientId: process.env.OAUTH_CLIENT_ID!,
  clientSecret: process.env.OAUTH_CLIENT_SECRET!,
  authorizationUrl: 'https://provider.com/oauth/authorize',
  tokenUrl: 'https://provider.com/oauth/token',
  redirectUri: process.env.OAUTH_REDIRECT_URI!,
  scopes: ['openid', 'profile', 'email'],
};

// Generate PKCE challenge
function generateCodeVerifier(): string {
  return randomBytes(32).toString('base64url');
}

function generateCodeChallenge(verifier: string): string {
  return createHash('sha256').update(verifier).digest('base64url');
}

// Generate state for CSRF protection
function generateState(): string {
  return randomBytes(16).toString('hex');
}

export function getAuthorizationUrl(): {
  url: string;
  state: string;
  codeVerifier: string;
} {
  const state = generateState();
  const codeVerifier = generateCodeVerifier();
  const codeChallenge = generateCodeChallenge(codeVerifier);

  const params = new URLSearchParams({
    client_id: config.clientId,
    redirect_uri: config.redirectUri,
    response_type: 'code',
    scope: config.scopes.join(' '),
    state,
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
  });

  return {
    url: `${config.authorizationUrl}?${params}`,
    state,
    codeVerifier,
  };
}

export async function exchangeCodeForTokens(
  code: string,
  codeVerifier: string
): Promise<TokenResponse> {
  const response = await fetch(config.tokenUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: config.clientId,
      client_secret: config.clientSecret,
      code,
      redirect_uri: config.redirectUri,
      code_verifier: codeVerifier,
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new OAuthError(error.error_description || 'Token exchange failed');
  }

  return response.json();
}

interface TokenResponse {
  access_token: string;
  token_type: string;
  expires_in: number;
  refresh_token?: string;
  id_token?: string;
  scope: string;
}

Login Route

// app/api/auth/login/route.ts
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { getAuthorizationUrl } from '@/lib/auth/oauth';

export async function GET() {
  const { url, state, codeVerifier } = getAuthorizationUrl();

  // Store state and verifier in secure, httpOnly cookies
  const cookieStore = cookies();

  cookieStore.set('oauth_state', state, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 600, // 10 minutes
    path: '/',
  });

  cookieStore.set('code_verifier', codeVerifier, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 600,
    path: '/',
  });

  return NextResponse.redirect(url);
}

Callback Route

// app/api/auth/callback/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { exchangeCodeForTokens } from '@/lib/auth/oauth';
import { createSession } from '@/lib/auth/session';

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const code = searchParams.get('code');
  const state = searchParams.get('state');
  const error = searchParams.get('error');

  // Handle OAuth errors
  if (error) {
    const errorDescription = searchParams.get('error_description');
    return NextResponse.redirect(
      new URL(`/login?error=${encodeURIComponent(errorDescription || error)}`, request.url)
    );
  }

  // Validate state
  const cookieStore = cookies();
  const storedState = cookieStore.get('oauth_state')?.value;
  const codeVerifier = cookieStore.get('code_verifier')?.value;

  if (!state || state !== storedState) {
    return NextResponse.redirect(
      new URL('/login?error=invalid_state', request.url)
    );
  }

  if (!code || !codeVerifier) {
    return NextResponse.redirect(
      new URL('/login?error=missing_code', request.url)
    );
  }

  try {
    // Exchange code for tokens
    const tokens = await exchangeCodeForTokens(code, codeVerifier);

    // Create session with tokens
    await createSession(tokens);

    // Clear OAuth cookies
    cookieStore.delete('oauth_state');
    cookieStore.delete('code_verifier');

    return NextResponse.redirect(new URL('/dashboard', request.url));
  } catch (error) {
    console.error('OAuth callback error:', error);
    return NextResponse.redirect(
      new URL('/login?error=authentication_failed', request.url)
    );
  }
}

OpenID Connect Integration

OIDC Discovery

// lib/auth/oidc.ts
interface OIDCConfig {
  issuer: string;
  authorization_endpoint: string;
  token_endpoint: string;
  userinfo_endpoint: string;
  jwks_uri: string;
  scopes_supported: string[];
  response_types_supported: string[];
}

let cachedConfig: OIDCConfig | null = null;

export async function discoverOIDCConfig(issuer: string): Promise<OIDCConfig> {
  if (cachedConfig) return cachedConfig;

  const response = await fetch(`${issuer}/.well-known/openid-configuration`);

  if (!response.ok) {
    throw new Error('Failed to fetch OIDC configuration');
  }

  cachedConfig = await response.json();
  return cachedConfig;
}

ID Token Validation

// lib/auth/jwt.ts
import { createRemoteJWKSet, jwtVerify } from 'jose';
import { discoverOIDCConfig } from './oidc';

interface IDTokenClaims {
  iss: string;
  sub: string;
  aud: string | string[];
  exp: number;
  iat: number;
  nonce?: string;
  email?: string;
  email_verified?: boolean;
  name?: string;
  picture?: string;
}

let jwks: ReturnType<typeof createRemoteJWKSet> | null = null;

export async function verifyIdToken(
  idToken: string,
  expectedNonce?: string
): Promise<IDTokenClaims> {
  const config = await discoverOIDCConfig(process.env.OIDC_ISSUER!);

  if (!jwks) {
    jwks = createRemoteJWKSet(new URL(config.jwks_uri));
  }

  const { payload } = await jwtVerify(idToken, jwks, {
    issuer: config.issuer,
    audience: process.env.OAUTH_CLIENT_ID!,
  });

  // Verify nonce if provided (for implicit/hybrid flows)
  if (expectedNonce && payload.nonce !== expectedNonce) {
    throw new Error('Invalid nonce');
  }

  return payload as IDTokenClaims;
}

User Info Endpoint

// lib/auth/userinfo.ts
interface UserInfo {
  sub: string;
  email?: string;
  email_verified?: boolean;
  name?: string;
  given_name?: string;
  family_name?: string;
  picture?: string;
  locale?: string;
}

export async function fetchUserInfo(accessToken: string): Promise<UserInfo> {
  const config = await discoverOIDCConfig(process.env.OIDC_ISSUER!);

  const response = await fetch(config.userinfo_endpoint, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  if (!response.ok) {
    throw new Error('Failed to fetch user info');
  }

  return response.json();
}

Session Management

// lib/auth/session.ts
import { SignJWT, jwtVerify } from 'jose';
import { cookies } from 'next/headers';

const SESSION_SECRET = new TextEncoder().encode(process.env.SESSION_SECRET!);

interface Session {
  userId: string;
  email: string;
  accessToken: string;
  refreshToken?: string;
  expiresAt: number;
}

export async function createSession(tokens: TokenResponse): Promise<void> {
  const claims = await verifyIdToken(tokens.id_token!);

  const session: Session = {
    userId: claims.sub,
    email: claims.email!,
    accessToken: tokens.access_token,
    refreshToken: tokens.refresh_token,
    expiresAt: Date.now() + tokens.expires_in * 1000,
  };

  const jwt = await new SignJWT(session)
    .setProtectedHeader({ alg: 'HS256' })
    .setExpirationTime('7d')
    .sign(SESSION_SECRET);

  cookies().set('session', jwt, {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7, // 7 days
    path: '/',
  });
}

export async function getSession(): Promise<Session | null> {
  const sessionCookie = cookies().get('session')?.value;
  if (!sessionCookie) return null;

  try {
    const { payload } = await jwtVerify(sessionCookie, SESSION_SECRET);
    return payload as Session;
  } catch {
    return null;
  }
}

export async function refreshSession(): Promise<Session | null> {
  const session = await getSession();
  if (!session?.refreshToken) return null;

  // Check if access token is expired
  if (session.expiresAt > Date.now() + 60000) {
    return session; // Still valid
  }

  // Refresh tokens
  const tokens = await refreshAccessToken(session.refreshToken);
  await createSession(tokens);

  return getSession();
}

async function refreshAccessToken(refreshToken: string): Promise<TokenResponse> {
  const response = await fetch(config.tokenUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'refresh_token',
      client_id: config.clientId,
      client_secret: config.clientSecret,
      refresh_token: refreshToken,
    }),
  });

  if (!response.ok) {
    throw new Error('Token refresh failed');
  }

  return response.json();
}

Multiple Providers

// lib/auth/providers.ts
interface OAuthProvider {
  id: string;
  name: string;
  authorizationUrl: string;
  tokenUrl: string;
  userInfoUrl: string;
  clientId: string;
  clientSecret: string;
  scopes: string[];
  mapUserInfo: (data: any) => UserProfile;
}

export const providers: Record<string, OAuthProvider> = {
  google: {
    id: 'google',
    name: 'Google',
    authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
    tokenUrl: 'https://oauth2.googleapis.com/token',
    userInfoUrl: 'https://www.googleapis.com/oauth2/v3/userinfo',
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    scopes: ['openid', 'email', 'profile'],
    mapUserInfo: (data) => ({
      id: data.sub,
      email: data.email,
      name: data.name,
      image: data.picture,
    }),
  },
  github: {
    id: 'github',
    name: 'GitHub',
    authorizationUrl: 'https://github.com/login/oauth/authorize',
    tokenUrl: 'https://github.com/login/oauth/access_token',
    userInfoUrl: 'https://api.github.com/user',
    clientId: process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    scopes: ['read:user', 'user:email'],
    mapUserInfo: (data) => ({
      id: String(data.id),
      email: data.email,
      name: data.name || data.login,
      image: data.avatar_url,
    }),
  },
  microsoft: {
    id: 'microsoft',
    name: 'Microsoft',
    authorizationUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
    tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
    userInfoUrl: 'https://graph.microsoft.com/v1.0/me',
    clientId: process.env.MICROSOFT_CLIENT_ID!,
    clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
    scopes: ['openid', 'email', 'profile', 'User.Read'],
    mapUserInfo: (data) => ({
      id: data.id,
      email: data.mail || data.userPrincipalName,
      name: data.displayName,
      image: null,
    }),
  },
};

Client Credentials Flow

// lib/auth/machine.ts
interface ClientCredentialsConfig {
  tokenUrl: string;
  clientId: string;
  clientSecret: string;
  scopes: string[];
}

let cachedToken: { token: string; expiresAt: number } | null = null;

export async function getMachineToken(config: ClientCredentialsConfig): Promise<string> {
  // Check cache
  if (cachedToken && cachedToken.expiresAt > Date.now() + 60000) {
    return cachedToken.token;
  }

  const response = await fetch(config.tokenUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      Authorization: `Basic ${Buffer.from(
        `${config.clientId}:${config.clientSecret}`
      ).toString('base64')}`,
    },
    body: new URLSearchParams({
      grant_type: 'client_credentials',
      scope: config.scopes.join(' '),
    }),
  });

  if (!response.ok) {
    throw new Error('Failed to get machine token');
  }

  const data = await response.json();

  cachedToken = {
    token: data.access_token,
    expiresAt: Date.now() + data.expires_in * 1000,
  };

  return cachedToken.token;
}

Best Practices

  1. Always use PKCE: Even for confidential clients
  2. Validate state: Prevent CSRF attacks
  3. Verify tokens: Check signature and claims
  4. Secure storage: HttpOnly cookies for tokens
  5. Refresh proactively: Before expiration
  6. Handle errors gracefully: Clear messaging
  7. Use HTTPS: Always in production
  8. Limit scopes: Request minimum needed

Output Checklist

Every OAuth/OIDC implementation should include:

  • PKCE code verifier/challenge
  • State parameter for CSRF
  • Secure token storage
  • Token refresh mechanism
  • ID token validation
  • Session management
  • Logout handling
  • Error handling
  • Multiple provider support
  • HTTPS enforcement

平台分布

Claude Code

29.5%
按下载量换算361

Gemini CLI

24.53%
按下载量换算300

Antigravity

16.84%
按下载量换算206

github-copilot

14.64%
按下载量换算179

Codex

8.23%
按下载量换算101

trae

3.54%
按下载量换算43

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

接触敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills