简体   繁体   English

使用 node-oidc-provider 完成交互后访问被拒绝

[英]Access Denied after interactionFinished using node-oidc-provider

I'm using node-oidc-provider in a custom framework, adonisjs to be specific.我在自定义框架中使用 node-oidc-provider,具体来说是 adonisjs。

At this point, I can visit http://localhost:3333/auth?client_id=foo&redirect_uri=http://localhost:3333/launch&response_type=code&scope=openid&nonce=123&state=321 and be redirected to the /interaction/:uid page where I am asked to log in.此时,我可以访问http://localhost:3333/auth?client_id=foo&redirect_uri=http://localhost:3333/launch&response_type=code&scope=openid&nonce=123&state=321并被重定向到 /interaction/:uid 页面,其中我被要求登录。

When I hit submit, I do call interactionFinished as stated in the documentation.当我点击提交时,我确实按照文档中的说明调用了interactionFinished I'll then get redirected to the /auth/:interaction_id page and eventually redirected to my redirect_uri with access_denied error.然后我将被重定向到/auth/:interaction_id页面,并最终重定向到我的 redirect_uri 并出现 access_denied 错误。

There's no hint as to why I'm getting access denied.没有任何提示说明为什么我会被拒绝访问。 Upon further digging though, I noticed that when interactionFinished is called, the interaction key is deleted in Redis.经过进一步挖掘,我注意到当调用interactionFinished时,Redis 中的交互键被删除。 (Not sure if it's relevant.) (不确定是否相关。)

Any ideas?有任何想法吗?

Configuration配置

const provider = new Provider('http://localhost:3333', {
  adapter: RedisAdapter,
  clients: [
    {
      client_id: 'foo',
      client_secret: 'bar',
      redirect_uris: ['http://localhost:3333/launch'],
      scope: 'openid',
      response_types: ['code'],
    },
  ],
  pkce: {
    required: () => false,
    methods: ['plain'],
  },
  async findAccount(ctx, id) {
    return {
      accountId: id,
      async claims(use, scope) {
        return { sub: id }
      },
    }
  },
  claims: {
    openid: ['sub'],
    email: ['email', 'email_verified'],
    phone: ['phone_number', 'phone_number_verified'],
    profile: [
      'birthdate',
      'family_name',
      'gender',
      'given_name',
      'locale',
      'middle_name',
      'name',
      'nickname',
      'picture',
      'preferred_username',
      'profile',
      'updated_at',
      'website',
      'zoneinfo',
    ],
  },
  interactions: {
    policy: basePolicy,
  },
  cookies: {
    keys: ['some secret key', 'and also the old rotated away some time ago', 'and one more'],
  },
  jwks: {
    keys: [
      {
        d: 'VEZOsY07JTFzGTqv6cC2Y32vsfChind2I_TTuvV225_-0zrSej3XLRg8iE_u0-3GSgiGi4WImmTwmEgLo4Qp3uEcxCYbt4NMJC7fwT2i3dfRZjtZ4yJwFl0SIj8TgfQ8ptwZbFZUlcHGXZIr4nL8GXyQT0CK8wy4COfmymHrrUoyfZA154ql_OsoiupSUCRcKVvZj2JHL2KILsq_sh_l7g2dqAN8D7jYfJ58MkqlknBMa2-zi5I0-1JUOwztVNml_zGrp27UbEU60RqV3GHjoqwI6m01U7K0a8Q_SQAKYGqgepbAYOA-P4_TLl5KC4-WWBZu_rVfwgSENwWNEhw8oQ',
        dp: 'E1Y-SN4bQqX7kP-bNgZ_gEv-pixJ5F_EGocHKfS56jtzRqQdTurrk4jIVpI-ZITA88lWAHxjD-OaoJUh9Jupd_lwD5Si80PyVxOMI2xaGQiF0lbKJfD38Sh8frRpgelZVaK_gm834B6SLfxKdNsP04DsJqGKktODF_fZeaGFPH0',
        dq: 'F90JPxevQYOlAgEH0TUt1-3_hyxY6cfPRU2HQBaahyWrtCWpaOzenKZnvGFZdg-BuLVKjCchq3G_70OLE-XDP_ol0UTJmDTT-WyuJQdEMpt_WFF9yJGoeIu8yohfeLatU-67ukjghJ0s9CBzNE_LrGEV6Cup3FXywpSYZAV3iqc',
        e: 'AQAB',
        kty: 'RSA',
        n: 'xwQ72P9z9OYshiQ-ntDYaPnnfwG6u9JAdLMZ5o0dmjlcyrvwQRdoFIKPnO65Q8mh6F_LDSxjxa2Yzo_wdjhbPZLjfUJXgCzm54cClXzT5twzo7lzoAfaJlkTsoZc2HFWqmcri0BuzmTFLZx2Q7wYBm0pXHmQKF0V-C1O6NWfd4mfBhbM-I1tHYSpAMgarSm22WDMDx-WWI7TEzy2QhaBVaENW9BKaKkJklocAZCxk18WhR0fckIGiWiSM5FcU1PY2jfGsTmX505Ub7P5Dz75Ygqrutd5tFrcqyPAtPTFDk8X1InxkkUwpP3nFU5o50DGhwQolGYKPGtQ-ZtmbOfcWQ',
        p: '5wC6nY6Ev5FqcLPCqn9fC6R9KUuBej6NaAVOKW7GXiOJAq2WrileGKfMc9kIny20zW3uWkRLm-O-3Yzze1zFpxmqvsvCxZ5ERVZ6leiNXSu3tez71ZZwp0O9gys4knjrI-9w46l_vFuRtjL6XEeFfHEZFaNJpz-lcnb3w0okrbM',
        q: '3I1qeEDslZFB8iNfpKAdWtz_Wzm6-jayT_V6aIvhvMj5mnU-Xpj75zLPQSGa9wunMlOoZW9w1wDO1FVuDhwzeOJaTm-Ds0MezeC4U6nVGyyDHb4CUA3ml2tzt4yLrqGYMT7XbADSvuWYADHw79OFjEi4T3s3tJymhaBvy1ulv8M',
        qi: 'wSbXte9PcPtr788e713KHQ4waE26CzoXx-JNOgN0iqJMN6C4_XJEX-cSvCZDf4rh7xpXN6SGLVd5ibIyDJi7bbi5EQ5AXjazPbLBjRthcGXsIuZ3AtQyR0CEWNSdM7EyM5TRdyZQ9kftfz9nI03guW3iKKASETqX2vh0Z8XRjyU',
        use: 'sig',
      },
      {
        crv: 'P-256',
        d: 'K9xfPv773dZR22TVUB80xouzdF7qCg5cWjPjkHyv7Ws',
        kty: 'EC',
        use: 'sig',
        x: 'FWZ9rSkLt6Dx9E3pxLybhdM6xgR5obGsj5_pqmnz5J4',
        y: '_n8G69C-A2Xl4xUW2lF0i8ZGZnk_KPYrhv4GbTGu5G4',
      },
    ],
  },
  features: {
    devInteractions: { enabled: true }, // defaults to true

    deviceFlow: { enabled: true }, // defaults to false
    revocation: { enabled: true }, // defaults to false
  },
})

Routes路线

Route.post('/interaction/:uid', async ({ request, response }) => {
  const details = await provider.interactionDetails(request.request, response.response)

  console.log(details)
  console.log(request.input('login'))

  return await provider.interactionFinished(request.request, response.response, {
    login: {
      accountId: request.input('login'),
    },
  })
})

拒绝访问

Okay, I found the solution to my own problem.好的,我找到了解决我自己问题的方法。

What I have missed to mention is that I disabled the consent prompt of the provider.我错过了提及的是我禁用了提供者的同意提示。

The problem is it doesn't generate a "Grant" automatically.问题是它不会自动生成“授予”。 As a result, the middleware "authorization/interaction.js" fails.结果,中间件“authorization/interaction.js”失败了。

    if (
      !oidc.grant.getOIDCScopeFiltered(oidc.requestParamOIDCScopes)
      && Object.keys(ctx.oidc.resourceServers)
        .every(
          (resource) => !oidc.grant.getResourceScopeFiltered(resource, oidc.requestParamScopes),
        )
    ) {

      throw new errors.AccessDenied(undefined, 'authorization request resolved without requesting interactions but no scope was granted');
    }

In order to resolve this, I have to override the loadExistingGrant and create a new instance of grant if it doesn't exist as explained in the documentation as well.为了解决这个问题,我必须重写loadExistingGrant并创建一个新的授权实例,如果它不存在,如文档中所述。

You may be required to skip (silently accept) some of the consent checks, while it is discouraged there are valid reasons to do that, for instance in some first-party scenarios or going with pre-existing, previously granted, consents.您可能需要跳过(默默接受)一些同意检查,但不鼓励这样做是有正当理由的,例如在某些第一方场景中或使用预先存在的、先前授予的同意。 To simply silenty "accept" first-party/resource indicated scopes or pre-agreed upon claims use the loadExistingGrant configuration helper function, in there you may just instantiate (and save.) a grant for the current clientId and accountId values.要简单地“接受”第一方/资源指示的范围或预先同意声明,请使用 loadExistingGrant 配置助手 function,您可以在其中实例化(并保存)当前 clientId 和 accountId 值的授权。

  async loadExistingGrant(ctx) {
    const grantId =
      (ctx.oidc.result && ctx.oidc.result.consent && ctx.oidc.result.consent.grantId) ||
      ctx.oidc.session.grantIdFor(ctx.oidc.client.clientId)

    if (grantId) {
      return ctx.oidc.provider.Grant.find(grantId)
    }

    const grant = new ctx.oidc.provider.Grant({
      clientId: ctx.oidc.client.clientId,
      accountId: ctx.oidc.session?.accountId,
    })

    const scopes = ctx.oidc.params.scope.split(' ')

    for (let scope of scopes) {
      // TODO: Confirm if this is correct
      grant.addOIDCScope(scope)
    }

    // TODO: Use ttl.Grant
    await grant.save(3600)

    return grant
  }

** Disclaimer: I'm still testing the code above but this is the gist of what I did to fix the problem. ** 免责声明:我仍在测试上面的代码,但这是我为解决问题所做的要点。

As a side tip, implement the renderError config ASAP so that you can see the full stacktrace instead of relying on the UI.作为一个小提示,尽快实现renderError配置,这样您就可以看到完整的堆栈跟踪,而不是依赖 UI。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM