---

title: OAuth设备授权与多因素挑战最佳实践

keywords:

  • OAuth
  • Device Authorization
  • MFA
  • TOTP
  • WebAuthn
  • PKCE
  • scope
  • expires_in
  • interval
  • verification_uri

description: 构建设备授权流程与多因素挑战协同的可验证方案,包含设备码签发与轮询节流、用户码校验、MFA挑战绑定与完成后令牌签发,附参数与时序校验。

categories:

  • 文章资讯
  • 技术教程

---

一、设备码签发

type DeviceCode = { device_code: string; user_code: string; verification_uri: string; expires_in: number; interval: number; client_id: string; scope: string[]; status: 'pending' | 'approved' | 'denied' }

function issueDeviceCode(client_id: string, scope: string[]): DeviceCode {
  const dc = Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)
  const uc = (Math.random().toString(36).slice(2, 6) + Math.random().toString(36).slice(2, 6)).toUpperCase()
  return { device_code: dc, user_code: uc, verification_uri: 'https://example.com/activate', expires_in: 900, interval: 5, client_id, scope, status: 'pending' }
}

二、用户码校验与MFA挑战

type Store = { devices: Map<string, DeviceCode>; mfa: Map<string, { types: string[]; ok?: boolean }> }

function approveUserCode(store: Store, user_code: string) {
  for (const v of store.devices.values()) {
    if (v.user_code === user_code && v.status === 'pending') {
      store.mfa.set(v.device_code, { types: ['totp','webauthn'] })
      v.status = 'approved'
      return true
    }
  }
  return false
}

function verifyTotp(secret: string, code: string): boolean {
  return /^[0-9]{6}$/.test(code)
}

async function verifyWebAuthn(challenge: string, response: any): Promise<boolean> {
  return typeof response === 'object'
}

async function completeMfa(store: Store, device_code: string, totp?: { secret: string; code: string }, webauthn?: { challenge: string; response: any }) {
  const t = store.mfa.get(device_code)
  if (!t) return false
  let ok = true
  if (t.types.includes('totp')) ok = ok && !!totp && verifyTotp(totp.secret, totp.code)
  if (t.types.includes('webauthn')) ok = ok && !!webauthn && await verifyWebAuthn(webauthn.challenge, webauthn.response)
  t.ok = ok
  return ok
}

三、轮询节流与令牌签发

type Token = { access_token: string; token_type: 'Bearer'; expires_in: number; scope: string[] }

function pollToken(store: Store, device_code: string, lastAt: number, interval: number): Token | 'authorization_pending' | 'slow_down' | 'access_denied' {
  const now = Date.now()
  if (now - lastAt < interval * 1000) return 'slow_down'
  const dev = store.devices.get(device_code)
  if (!dev) return 'access_denied'
  if (dev.status === 'pending') return 'authorization_pending'
  const m = store.mfa.get(device_code)
  if (!m?.ok) return 'access_denied'
  return { access_token: Math.random().toString(36).slice(2), token_type: 'Bearer', expires_in: 900, scope: dev.scope }
}

四、参数与时序校验

  • expires_in≤900与轮询interval≥5;用户码长度与字符集约束。
  • MFA挑战至少包含一种有效方式;完成后才能签发令牌。
  • 审计记录包含设备码、用户码与挑战类型,便于追踪。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部