Skip to content

feat(auth): add auth headers provider and interceptor options#255

Open
egalvis27 wants to merge 3 commits intomainfrom
feat/add-authentication-middleware
Open

feat(auth): add auth headers provider and interceptor options#255
egalvis27 wants to merge 3 commits intomainfrom
feat/add-authentication-middleware

Conversation

@egalvis27
Copy link

What is Changed / Added


The assignment of authentication headers has been centralized so that it does not have to be repeated for each request.

Why

@egalvis27 egalvis27 requested a review from AlexisMora February 25, 2026 01:33
@@ -0,0 +1,6 @@
export type AuthHeadersProvider = () => Promise<Record<string, string>> | Record<string, string>;

export interface AuthInterceptorOptions {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this type is exactly the same as ClientOptions
you could do something like export type AuthInterceptorOptions = Omit<ClientOptions, 'baseUrl'> or even request it like this in the param where its used

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines +7 to +11
if (options.authHeadersProvider) {
instance.interceptors.request.use(createRequestInterceptor(options.authHeadersProvider));
}

if (options.onUnauthorized) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a look a this, does it really need to be optional these handler?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is necessary to keep it that way; the auth client does not send those parameters. In the future, we should try to unify those clients; it is very rare to have so many.

Comment on lines +4 to +21
function getHeaderValue(headers: unknown, key: string): string | undefined {
if (!headers || typeof headers !== 'object') {
return undefined;
}

if ('get' in headers && typeof headers.get === 'function') {
const value = headers.get(key);
return typeof value === 'string' ? value : undefined;
}

for (const [headerKey, headerValue] of Object.entries(headers as Record<string, unknown>)) {
if (headerKey.toLowerCase() === key.toLowerCase() && typeof headerValue === 'string') {
return headerValue;
}
}

return undefined;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this function actually needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is better to keep the function, avoid problems with duplicate headers or replacement of headers sent directly from the request.

});
});

it('should not overwrite a header already present in the request', async () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then, we would have no way to refresh the token?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no way to do this. We would have to verify for each request whether the token is about to expire, and due to race conditions, it is possible that two requests could refresh and end up revoking each other's tokens, causing problems.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could try having some shared state across requests to work around the race condition right?

expect((result.headers as Record<string, string>).Authorization).toBe('Bearer existing');
});

it('should resolve async authHeadersProvider', async () => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is the counterpart test of the last one right? if no header then add it


const onRejected = (error: AxiosError): Promise<never> => {
if (axios.isAxiosError(error) && error.response?.status === 401) {
onUnauthorized();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we are not going to refresh the token?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no way to do this. We would have to verify for each request whether the token is about to expire, and due to race conditions, it is possible that two requests could refresh and end up revoking each other's tokens, causing problems.

@sonarqubecloud
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants