Merge pull request #29198 from goququ/custom-oidc-timeout

Custom OIDC timeout
This commit is contained in:
Ben Lambert
2025-03-25 11:43:06 +01:00
committed by GitHub
5 changed files with 110 additions and 6 deletions
+17
View File
@@ -0,0 +1,17 @@
---
'@backstage/plugin-auth-backend-module-oidc-provider': patch
---
Added custom timeout setting for oidc provider
Here is an example of how to use a custom timeout with the configuration:
```yaml
auth:
oidc:
production:
clientId: ${AUTH_GOOGLE_CLIENT_ID}
clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
timeout:
seconds: 30
```
+1 -1
View File
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
@@ -33,6 +32,7 @@ export interface Config {
tokenSignedResponseAlg?: string;
additionalScopes?: string | string[];
prompt?: string;
timeout?: HumanDuration;
signIn?: {
resolvers: Array<
| {
@@ -35,8 +35,10 @@
},
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/types": "workspace:^",
"express": "^4.18.2",
"openid-client": "^5.5.0",
"passport": "^0.7.0"
@@ -28,6 +28,7 @@ import { ConfigReader } from '@backstage/config';
import { JWK, SignJWT, exportJWK, generateKeyPair } from 'jose';
import { rest } from 'msw';
import express from 'express';
import { custom } from 'openid-client';
describe('oidcAuthenticator', () => {
let implementation: any;
@@ -169,6 +170,77 @@ describe('oidcAuthenticator', () => {
jest.clearAllMocks();
});
describe('timeout configuration', () => {
const TEST_URL = new URL('https://test.com');
it('should use default timeout when no timeout is configured', async () => {
const { promise } = oidcAuthenticator.initialize({
callbackUrl: 'https://backstage.test/callback',
config: new ConfigReader({
metadataUrl: 'https://oidc.test/.well-known/openid-configuration',
clientId: 'clientId',
clientSecret: 'clientSecret',
}),
});
const { client } = await promise;
const timeout = client[custom.http_options](TEST_URL, {}).timeout;
// Check if the HTTP timeout is set to the default value
expect(timeout).toBeDefined();
expect(timeout).toBe(10000);
});
it('should use configured timeout when provided in the config', async () => {
const { promise } = oidcAuthenticator.initialize({
callbackUrl: 'https://backstage.test/callback',
config: new ConfigReader({
metadataUrl: 'https://oidc.test/.well-known/openid-configuration',
clientId: 'clientId',
clientSecret: 'clientSecret',
timeout: {
seconds: 30,
},
}),
});
const { client } = await promise;
const timeout = client[custom.http_options](TEST_URL, {}).timeout;
// Check if the HTTP timeout is set to the configured value (30 seconds)
expect(timeout).toBeDefined();
expect(timeout).toBe(30000);
});
it('should handle invalid timeout configuration gracefully', async () => {
expect(() => {
oidcAuthenticator.initialize({
callbackUrl: 'https://backstage.test/callback',
config: new ConfigReader({
metadataUrl: 'https://oidc.test/.well-known/openid-configuration',
clientId: 'clientId',
clientSecret: 'clientSecret',
timeout: 123, // Invalid: should be a duration object
}),
});
}).toThrow();
expect(() => {
oidcAuthenticator.initialize({
callbackUrl: 'https://backstage.test/callback',
config: new ConfigReader({
metadataUrl: 'https://oidc.test/.well-known/openid-configuration',
clientId: 'clientId',
clientSecret: 'clientSecret',
timeout: {
invalid: 'value',
},
}),
});
}).toThrow();
});
});
describe('#start', () => {
let fakeSession: Record<string, any>;
let startRequest: OAuthAuthenticatorStartInput;
@@ -32,14 +32,18 @@ import {
PassportOAuthAuthenticatorHelper,
PassportOAuthPrivateInfo,
} from '@backstage/plugin-auth-node';
import { durationToMilliseconds } from '@backstage/types';
import { readDurationFromConfig } from '@backstage/config';
const HTTP_OPTION_TIMEOUT = 10000;
const httpOptionsProvider: CustomHttpOptionsProvider = (_url, options) => {
return {
...options,
timeout: HTTP_OPTION_TIMEOUT,
const createHttpOptionsProvider =
({ timeout }: { timeout?: number }): CustomHttpOptionsProvider =>
(_url, options) => {
return {
...options,
timeout: timeout ?? HTTP_OPTION_TIMEOUT,
};
};
};
/**
* authentication result for the OIDC which includes the token set and user
@@ -85,6 +89,15 @@ export const oidcAuthenticator = createOAuthAuthenticator({
);
}
const timeoutMilliseconds = config.has('timeout')
? durationToMilliseconds(
readDurationFromConfig(config, { key: 'timeout' }),
)
: undefined;
const httpOptionsProvider = createHttpOptionsProvider({
timeout: timeoutMilliseconds,
});
Issuer[custom.http_options] = httpOptionsProvider;
const promise = Issuer.discover(metadataUrl).then(issuer => {
issuer[custom.http_options] = httpOptionsProvider;