Skip to content

Support async functions for tokenParams and providerParams #498

Description

@johnwatson484

Runtime

Node.js

Runtime version

24

Module version

13

Used with

Hapi application

Any other relevant information

No response

What problem are you trying to solve?

I'm building a Node.js service that authenticates users via Microsoft Entra ID using Bell.

The service runs on AWS and I want to use Federated Identity Credentials.

Federated identity means Entra accepts a short-lived JWT assertion that the app fetches from AWS STS, rather than a long-lived shared secret. Full details in RFC 7523.

Bell almost supports this as is, but requires two workarounds to get it working.

tokenParams does not support asynchronous functions

tokenParams is resolved synchronously by internals.resolveProviderParams. This means a function passed as tokenParams cannot be async - it cannot await a call to AWS STS to fetch the assertion token.

The workaround is to pre-fetch the token before the server starts up and store it in memory. I then have a background refresh loop to keep it current when the synchronous tokenParams needs to reference it.

That's obviously not ideal as it means I'm frequently fetching a fresh token even if no user needs it at that point.

clientSecret is required

I'm not sure if this is a workaround or intended usage, but I couldn't find it documented anywhere. As I don't have a clientSecret, to bypass Bell sending the client secret in the request, I have to pass the value as an empty object.

So combining the two workarounds above, I have config like this.

server.auth.strategy('entra', 'bell', {
  clientSecret: {}, // object prevents Bell adding client_secret to the request
  tokenParams: (_request) => getClientCredentialParams() // must be synchronous so call a function that returns current in memory token as `client_assertion` and `client_assetion_type` of `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`
})

Do you have a new or modified API suggestion to solve the problem?

Make internals.resolveProviderParams async and await it's result on each call.

That allows me to remove the background refreshing of the token entirely.

server.auth.strategy('entra', 'bell', {
  clientSecret: {},
  tokenParams: async (_request) => {
    const result = await stsClient.send(new GetWebIdentityTokenCommand({ ... }))
    return {
      client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
      client_assertion: result.IdentityToken
    }
  }
})

I have opened a pull request for consideration with this change.

It would also be good to clarify whether passing an empty object for clientSecret is intended behaviour and won't stop working as a result of any refactoring.

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew functionality or improvement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions