[node oidc provider]: Authorization code flow

1.4k Views Asked by At

I am trying to build an OAuth2.0 server using https://www.npmjs.com/package/oidc-provider.

The 2 main flows that I need to support are the client credentials one and the authorization code flow. For the latter the idea is to send an authorization code (OTP) to the user's email and exchange that for an access token.

Is the above achievable with this library? If not, are there any alternatives you could recommend, which are in working order and actively maintained?

Here is the request I am making to the /auth endpoint:

enter image description here

The above requests redirects me to the /interaction/:uid endpoint, where I am presented with the following details:

{"iat":1658821468,"exp":1658825068,"returnTo":"http://localhost:3000/oidc/auth/T6W8Cl7_bxYiiYC2AxG3f","prompt":{"name":"login","reasons":["no_session"],"details":{}},"params":{"client_id":"foo","code_challenge":"nELIDtDAvjgo5Hn0eh7mx4JmfFbfWZ166nwXwg89_zs","code_challenge_method":"S256","redirect_uri":"https://oauthdebugger.com/debug","response_mode":"query","response_type":"code"},"kind":"Interaction","jti":"T6W8Cl7_bxYiiYC2AxG3f"}

I may be wrong in how I understand the flow, but I expected to have a code field in the response / code parameter in the redirect url that I can then send to the user's email and (not exactly sure how) exchange it for the access token.

This is my current progress with the POC:

const { Provider } = require('oidc-provider');
const express = require('express');
const cors = require('cors')

const PORT = 3000;
const app = express();

const oidcPrefix = '/oidc';

app.use(cors());

const oidc = new Provider('http://localhost:3000', {
  clients: [{
    client_id: 'foo',
    client_secret: 'bar',
    allowOmittingSingleRegisteredRedirectUri: true,
    redirect_uris: ['https://oauthdebugger.com/debug'],
    rotateRefreshToken: true,
    pkce: {
      required: true
    },
    grant_types: ['authorization_code', 'refresh_token'],
    response_types: ['code'],
    token_endpoint_auth_method: "client_secret_basic"
  }],
  interactions: {
    url: (_, interaction) => `${oidcPrefix}/interaction/${interaction.uid}`
  },
  features: {
    devInteractions: {
      enabled: false,
    },
    introspection: {
      enabled: true
    },
  },
  formats: {
    AccessToken: 'jwt',
  },
  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',
      },
    ],
  },
});

app.get(`${oidcPrefix}/interaction/:id`, async (req, res) => {
  const details = await oidc.interactionDetails(req, res);
  res.send(JSON.stringify(details))
});

app.get(`${oidcPrefix}/auth/:id`, async (req, res) => {
  const details = await oidc.interactionDetails(req, res);

  res.send(JSON.stringify(details))
});

app.use(oidcPrefix, oidc.callback());

app.listen(PORT, () => {
  console.log(`OAUTH Server listening on ${PORT}`);
})

I am open for any suggestions. Also, if you need me to provide additional details, do let me know. Thank you in advance!

1

There are 1 best solutions below

4
Codebling On

Generally follow the section on User Flows in the documentation.

//The library will send the user to this route for authentication
app.get('/interaction/:uid', async (req, res) => {
  //Send email with OTP and link to `/interaction/:uid/login&otp=xxx`

  //It's common to also provide a from to let the user manually enter their OTP code instead of clicking on the link
  //Display the form here, it should redirect to `/interaction/:uid/login&otp=xxx`
  //otherwise just display a message saying OTP link was sent
});

//This is the route that would normally be used to POST password login forms
app.post('/interaction/:uid/login', async (req, res) => {
  //verify OTP in POST data or query `&otp=xxx`

  //if not valid, display error

  //if valid, call `interactionFinished`
  //this indicates to oidc-provider that the user has successfully authenticated
  return provider.interactionFinished(req, res, { login: { accountId: 'abc123' }});
);

That's about it