hapijs / hapijs/bell

Support async functions for tokenParams and providerParams

Open
#498 1 comment 1 reaction 1 assignee Claimed by @Marsup View on GitHub
feature
Dominant language
JavaScript
Stars
616
Forks
210
Avg merge
3m
Merged PRs (30d)
3

Description

### 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](https://datatracker.ietf.org/doc/html/rfc7523).

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.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.