@backstage/plugin-auth-backend 0.28.1-next.0 → 0.28.1-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @backstage/plugin-auth-backend
2
2
 
3
+ ## 0.28.1-next.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 4f62755: Improved the OAuth consent dialog for MCP authorization by showing more client details, including the client metadata host for CIMD clients, the metadata URL, callback URL, and requested scopes.
8
+ - Updated dependencies
9
+ - @backstage/backend-plugin-api@1.9.1-next.1
10
+
11
+ ## 0.28.1-next.1
12
+
13
+ ### Patch Changes
14
+
15
+ - e9b78e9: Removed the `uuid` dependency and replaced usage with the built-in `crypto.randomUUID()`.
16
+ - Updated dependencies
17
+ - @backstage/catalog-model@1.8.1-next.1
18
+ - @backstage/plugin-catalog-node@2.2.1-next.1
19
+ - @backstage/plugin-auth-node@0.7.1-next.1
20
+
3
21
  ## 0.28.1-next.0
4
22
 
5
23
  ### Patch Changes
@@ -2,7 +2,7 @@
2
2
 
3
3
  var jose = require('jose');
4
4
  var luxon = require('luxon');
5
- var uuid = require('uuid');
5
+ var crypto = require('node:crypto');
6
6
  var issueUserToken = require('./issueUserToken.cjs.js');
7
7
 
8
8
  class TokenFactory {
@@ -74,7 +74,7 @@ class TokenFactory {
74
74
  const key = await jose.generateKeyPair(this.algorithm);
75
75
  const publicKey = await jose.exportJWK(key.publicKey);
76
76
  const privateKey = await jose.exportJWK(key.privateKey);
77
- publicKey.kid = privateKey.kid = uuid.v4();
77
+ publicKey.kid = privateKey.kid = crypto.randomUUID();
78
78
  publicKey.alg = privateKey.alg = this.algorithm;
79
79
  this.logger.info(`Created new signing key ${publicKey.kid}`);
80
80
  await this.keyStore.addKey(publicKey);
@@ -1 +1 @@
1
- {"version":3,"file":"TokenFactory.cjs.js","sources":["../../src/identity/TokenFactory.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { exportJWK, generateKeyPair, JWK } from 'jose';\nimport { DateTime } from 'luxon';\nimport { v4 as uuid } from 'uuid';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n BackstageSignInResult,\n TokenParams,\n tokenTypes,\n} from '@backstage/plugin-auth-node';\nimport { AnyJWK, KeyStore, TokenIssuer } from './types';\nimport { JsonValue } from '@backstage/types';\nimport { issueUserToken } from './issueUserToken';\n\n/**\n * The payload contents of a valid Backstage JWT token\n */\nexport interface BackstageTokenPayload {\n /**\n * The issuer of the token, currently the discovery URL of the auth backend\n */\n iss: string;\n\n /**\n * The entity ref of the user\n */\n sub: string;\n\n /**\n * The entity refs that the user claims ownership through\n */\n ent: string[];\n\n /**\n * A hard coded audience string\n */\n aud: typeof tokenTypes.user.audClaim;\n\n /**\n * Standard expiry in epoch seconds\n */\n exp: number;\n\n /**\n * Standard issue time in epoch seconds\n */\n iat: number;\n\n /**\n * A separate user identity proof that the auth service can convert to a limited user token\n */\n uip: string;\n\n /**\n * Any other custom claims that the adopter may have added\n */\n [claim: string]: JsonValue;\n}\n\ntype Options = {\n logger: LoggerService;\n /** Value of the issuer claim in issued tokens */\n issuer: string;\n /** Key store used for storing signing keys */\n keyStore: KeyStore;\n /** Expiration time of signing keys in seconds */\n keyDurationSeconds: number;\n /** JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose */\n algorithm?: string;\n /**\n * A list of claims to omit from issued tokens and only store in the user info database\n */\n omitClaimsFromToken?: string[];\n};\n\n/**\n * A token issuer that is able to issue tokens in a distributed system\n * backed by a single database. Tokens are issued using lazily generated\n * signing keys, where each running instance of the auth service uses its own\n * signing key.\n *\n * The public parts of the keys are all stored in the shared key storage,\n * and any of the instances of the auth service will return the full list\n * of public keys that are currently in storage.\n *\n * Signing keys are automatically rotated at the same interval as the token\n * duration. Expired keys are kept in storage until there are no valid tokens\n * in circulation that could have been signed by that key.\n */\nexport class TokenFactory implements TokenIssuer {\n private readonly issuer: string;\n private readonly logger: LoggerService;\n private readonly keyStore: KeyStore;\n private readonly keyDurationSeconds: number;\n private readonly algorithm: string;\n private readonly omitClaimsFromToken?: string[];\n\n private keyExpiry?: Date;\n private privateKeyPromise?: Promise<JWK>;\n\n constructor(options: Options) {\n this.issuer = options.issuer;\n this.logger = options.logger;\n this.keyStore = options.keyStore;\n this.keyDurationSeconds = options.keyDurationSeconds;\n this.algorithm = options.algorithm ?? 'ES256';\n this.omitClaimsFromToken = options.omitClaimsFromToken;\n }\n\n async issueToken(\n params: TokenParams & { claims: { ent: string[] } },\n ): Promise<BackstageSignInResult> {\n const key = await this.getKey();\n\n return issueUserToken({\n issuer: this.issuer,\n key,\n keyDurationSeconds: this.keyDurationSeconds,\n logger: this.logger,\n omitClaimsFromToken: this.omitClaimsFromToken,\n params,\n });\n }\n\n // This will be called by other services that want to verify ID tokens.\n // It is important that it returns a list of all public keys that could\n // have been used to sign tokens that have not yet expired.\n async listPublicKeys(): Promise<{ keys: AnyJWK[] }> {\n const { items: keys } = await this.keyStore.listKeys();\n\n const validKeys = [];\n const expiredKeys = [];\n\n for (const key of keys) {\n // Allow for a grace period of another full key duration before we remove the keys from the database\n const expireAt = DateTime.fromJSDate(key.createdAt).plus({\n seconds: 3 * this.keyDurationSeconds,\n });\n if (expireAt < DateTime.local()) {\n expiredKeys.push(key);\n } else {\n validKeys.push(key);\n }\n }\n\n // Lazily prune expired keys. This may cause duplicate removals if we have concurrent callers, but w/e\n if (expiredKeys.length > 0) {\n const kids = expiredKeys.map(({ key }) => key.kid);\n\n this.logger.info(`Removing expired signing keys, '${kids.join(\"', '\")}'`);\n\n // We don't await this, just let it run in the background\n this.keyStore.removeKeys(kids).catch(error => {\n this.logger.error(`Failed to remove expired keys, ${error}`);\n });\n }\n\n // NOTE: we're currently only storing public keys, but if we start storing private keys we'd have to convert here\n return { keys: validKeys.map(({ key }) => key) };\n }\n\n private async getKey(): Promise<JWK> {\n // Make sure that we only generate one key at a time\n if (this.privateKeyPromise) {\n if (\n this.keyExpiry &&\n DateTime.fromJSDate(this.keyExpiry) > DateTime.local()\n ) {\n return this.privateKeyPromise;\n }\n this.logger.info(`Signing key has expired, generating new key`);\n delete this.privateKeyPromise;\n }\n\n this.keyExpiry = DateTime.utc()\n .plus({\n seconds: this.keyDurationSeconds,\n })\n .toJSDate();\n const promise = (async () => {\n // This generates a new signing key to be used to sign tokens until the next key rotation\n const key = await generateKeyPair(this.algorithm);\n const publicKey = await exportJWK(key.publicKey);\n const privateKey = await exportJWK(key.privateKey);\n publicKey.kid = privateKey.kid = uuid();\n publicKey.alg = privateKey.alg = this.algorithm;\n\n // We're not allowed to use the key until it has been successfully stored\n // TODO: some token verification implementations aggressively cache the list of keys, and\n // don't attempt to fetch new ones even if they encounter an unknown kid. Therefore we\n // may want to keep using the existing key for some period of time until we switch to\n // the new one. This also needs to be implemented cross-service though, meaning new services\n // that boot up need to be able to grab an existing key to use for signing.\n this.logger.info(`Created new signing key ${publicKey.kid}`);\n await this.keyStore.addKey(publicKey as AnyJWK);\n\n // At this point we are allowed to start using the new key\n return privateKey;\n })();\n\n this.privateKeyPromise = promise;\n\n try {\n // If we fail to generate a new key, we need to clear the state so that\n // the next caller will try to generate another key.\n await promise;\n } catch (error) {\n this.logger.error(`Failed to generate new signing key, ${error}`);\n delete this.keyExpiry;\n delete this.privateKeyPromise;\n }\n\n return promise;\n }\n}\n"],"names":["issueUserToken","DateTime","generateKeyPair","exportJWK","uuid"],"mappings":"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"}
1
+ {"version":3,"file":"TokenFactory.cjs.js","sources":["../../src/identity/TokenFactory.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { exportJWK, generateKeyPair, JWK } from 'jose';\nimport { DateTime } from 'luxon';\nimport { randomUUID as uuid } from 'node:crypto';\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport {\n BackstageSignInResult,\n TokenParams,\n tokenTypes,\n} from '@backstage/plugin-auth-node';\nimport { AnyJWK, KeyStore, TokenIssuer } from './types';\nimport { JsonValue } from '@backstage/types';\nimport { issueUserToken } from './issueUserToken';\n\n/**\n * The payload contents of a valid Backstage JWT token\n */\nexport interface BackstageTokenPayload {\n /**\n * The issuer of the token, currently the discovery URL of the auth backend\n */\n iss: string;\n\n /**\n * The entity ref of the user\n */\n sub: string;\n\n /**\n * The entity refs that the user claims ownership through\n */\n ent: string[];\n\n /**\n * A hard coded audience string\n */\n aud: typeof tokenTypes.user.audClaim;\n\n /**\n * Standard expiry in epoch seconds\n */\n exp: number;\n\n /**\n * Standard issue time in epoch seconds\n */\n iat: number;\n\n /**\n * A separate user identity proof that the auth service can convert to a limited user token\n */\n uip: string;\n\n /**\n * Any other custom claims that the adopter may have added\n */\n [claim: string]: JsonValue;\n}\n\ntype Options = {\n logger: LoggerService;\n /** Value of the issuer claim in issued tokens */\n issuer: string;\n /** Key store used for storing signing keys */\n keyStore: KeyStore;\n /** Expiration time of signing keys in seconds */\n keyDurationSeconds: number;\n /** JWS \"alg\" (Algorithm) Header Parameter value. Defaults to ES256.\n * Must match one of the algorithms defined for IdentityClient.\n * When setting a different algorithm, check if the `key` field\n * of the `signing_keys` table can fit the length of the generated keys.\n * If not, add a knex migration file in the migrations folder.\n * More info on supported algorithms: https://github.com/panva/jose */\n algorithm?: string;\n /**\n * A list of claims to omit from issued tokens and only store in the user info database\n */\n omitClaimsFromToken?: string[];\n};\n\n/**\n * A token issuer that is able to issue tokens in a distributed system\n * backed by a single database. Tokens are issued using lazily generated\n * signing keys, where each running instance of the auth service uses its own\n * signing key.\n *\n * The public parts of the keys are all stored in the shared key storage,\n * and any of the instances of the auth service will return the full list\n * of public keys that are currently in storage.\n *\n * Signing keys are automatically rotated at the same interval as the token\n * duration. Expired keys are kept in storage until there are no valid tokens\n * in circulation that could have been signed by that key.\n */\nexport class TokenFactory implements TokenIssuer {\n private readonly issuer: string;\n private readonly logger: LoggerService;\n private readonly keyStore: KeyStore;\n private readonly keyDurationSeconds: number;\n private readonly algorithm: string;\n private readonly omitClaimsFromToken?: string[];\n\n private keyExpiry?: Date;\n private privateKeyPromise?: Promise<JWK>;\n\n constructor(options: Options) {\n this.issuer = options.issuer;\n this.logger = options.logger;\n this.keyStore = options.keyStore;\n this.keyDurationSeconds = options.keyDurationSeconds;\n this.algorithm = options.algorithm ?? 'ES256';\n this.omitClaimsFromToken = options.omitClaimsFromToken;\n }\n\n async issueToken(\n params: TokenParams & { claims: { ent: string[] } },\n ): Promise<BackstageSignInResult> {\n const key = await this.getKey();\n\n return issueUserToken({\n issuer: this.issuer,\n key,\n keyDurationSeconds: this.keyDurationSeconds,\n logger: this.logger,\n omitClaimsFromToken: this.omitClaimsFromToken,\n params,\n });\n }\n\n // This will be called by other services that want to verify ID tokens.\n // It is important that it returns a list of all public keys that could\n // have been used to sign tokens that have not yet expired.\n async listPublicKeys(): Promise<{ keys: AnyJWK[] }> {\n const { items: keys } = await this.keyStore.listKeys();\n\n const validKeys = [];\n const expiredKeys = [];\n\n for (const key of keys) {\n // Allow for a grace period of another full key duration before we remove the keys from the database\n const expireAt = DateTime.fromJSDate(key.createdAt).plus({\n seconds: 3 * this.keyDurationSeconds,\n });\n if (expireAt < DateTime.local()) {\n expiredKeys.push(key);\n } else {\n validKeys.push(key);\n }\n }\n\n // Lazily prune expired keys. This may cause duplicate removals if we have concurrent callers, but w/e\n if (expiredKeys.length > 0) {\n const kids = expiredKeys.map(({ key }) => key.kid);\n\n this.logger.info(`Removing expired signing keys, '${kids.join(\"', '\")}'`);\n\n // We don't await this, just let it run in the background\n this.keyStore.removeKeys(kids).catch(error => {\n this.logger.error(`Failed to remove expired keys, ${error}`);\n });\n }\n\n // NOTE: we're currently only storing public keys, but if we start storing private keys we'd have to convert here\n return { keys: validKeys.map(({ key }) => key) };\n }\n\n private async getKey(): Promise<JWK> {\n // Make sure that we only generate one key at a time\n if (this.privateKeyPromise) {\n if (\n this.keyExpiry &&\n DateTime.fromJSDate(this.keyExpiry) > DateTime.local()\n ) {\n return this.privateKeyPromise;\n }\n this.logger.info(`Signing key has expired, generating new key`);\n delete this.privateKeyPromise;\n }\n\n this.keyExpiry = DateTime.utc()\n .plus({\n seconds: this.keyDurationSeconds,\n })\n .toJSDate();\n const promise = (async () => {\n // This generates a new signing key to be used to sign tokens until the next key rotation\n const key = await generateKeyPair(this.algorithm);\n const publicKey = await exportJWK(key.publicKey);\n const privateKey = await exportJWK(key.privateKey);\n publicKey.kid = privateKey.kid = uuid();\n publicKey.alg = privateKey.alg = this.algorithm;\n\n // We're not allowed to use the key until it has been successfully stored\n // TODO: some token verification implementations aggressively cache the list of keys, and\n // don't attempt to fetch new ones even if they encounter an unknown kid. Therefore we\n // may want to keep using the existing key for some period of time until we switch to\n // the new one. This also needs to be implemented cross-service though, meaning new services\n // that boot up need to be able to grab an existing key to use for signing.\n this.logger.info(`Created new signing key ${publicKey.kid}`);\n await this.keyStore.addKey(publicKey as AnyJWK);\n\n // At this point we are allowed to start using the new key\n return privateKey;\n })();\n\n this.privateKeyPromise = promise;\n\n try {\n // If we fail to generate a new key, we need to clear the state so that\n // the next caller will try to generate another key.\n await promise;\n } catch (error) {\n this.logger.error(`Failed to generate new signing key, ${error}`);\n delete this.keyExpiry;\n delete this.privateKeyPromise;\n }\n\n return promise;\n }\n}\n"],"names":["issueUserToken","DateTime","generateKeyPair","exportJWK","uuid"],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  var errors = require('@backstage/errors');
4
4
  var config = require('@backstage/config');
5
5
  var types = require('@backstage/types');
6
- var uuid = require('uuid');
6
+ var crypto = require('node:crypto');
7
7
  var OfflineSessionDatabase = require('../database/OfflineSessionDatabase.cjs.js');
8
8
  var refreshToken = require('../lib/refreshToken.cjs.js');
9
9
 
@@ -89,7 +89,7 @@ class OfflineAccessService {
89
89
  */
90
90
  async issueRefreshToken(options) {
91
91
  const { userEntityRef, oidcClientId } = options;
92
- const sessionId = uuid.v4();
92
+ const sessionId = crypto.randomUUID();
93
93
  const { token, hash } = await refreshToken.generateRefreshToken(sessionId);
94
94
  await this.#offlineSessionDb.createSession({
95
95
  id: sessionId,
@@ -1 +1 @@
1
- {"version":3,"file":"OfflineAccessService.cjs.js","sources":["../../src/service/OfflineAccessService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n DatabaseService,\n LifecycleService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { readDurationFromConfig } from '@backstage/config';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { v4 as uuid } from 'uuid';\nimport { OfflineSessionDatabase } from '../database/OfflineSessionDatabase';\nimport {\n generateRefreshToken,\n getRefreshTokenId,\n verifyRefreshToken,\n} from '../lib/refreshToken';\nimport { TokenIssuer } from '../identity/types';\n\n/**\n * Service for managing offline access (refresh tokens)\n * @internal\n */\nexport class OfflineAccessService {\n readonly #offlineSessionDb: OfflineSessionDatabase;\n readonly #logger: LoggerService;\n\n static async create(options: {\n config: RootConfigService;\n database: DatabaseService;\n logger: LoggerService;\n lifecycle: LifecycleService;\n }): Promise<OfflineAccessService> {\n const { config, database, logger, lifecycle } = options;\n\n const tokenLifetime = config.has(\n 'auth.experimentalRefreshToken.tokenLifetime',\n )\n ? readDurationFromConfig(config, {\n key: 'auth.experimentalRefreshToken.tokenLifetime',\n })\n : { days: 30 };\n\n const maxRotationLifetime = config.has(\n 'auth.experimentalRefreshToken.maxRotationLifetime',\n )\n ? readDurationFromConfig(config, {\n key: 'auth.experimentalRefreshToken.maxRotationLifetime',\n })\n : { years: 1 };\n\n const tokenLifetimeSeconds = Math.floor(\n durationToMilliseconds(tokenLifetime) / 1000,\n );\n const maxRotationLifetimeSeconds = Math.floor(\n durationToMilliseconds(maxRotationLifetime) / 1000,\n );\n\n if (tokenLifetimeSeconds <= 0) {\n throw new Error(\n 'auth.experimentalRefreshToken.tokenLifetime must be a positive duration',\n );\n }\n if (maxRotationLifetimeSeconds <= 0) {\n throw new Error(\n 'auth.experimentalRefreshToken.maxRotationLifetime must be a positive duration',\n );\n }\n if (maxRotationLifetimeSeconds <= tokenLifetimeSeconds) {\n throw new Error(\n 'auth.experimentalRefreshToken.maxRotationLifetime must be greater than tokenLifetime',\n );\n }\n\n const maxTokensPerUser =\n config.getOptionalNumber(\n 'auth.experimentalRefreshToken.maxTokensPerUser',\n ) ?? 20;\n\n if (maxTokensPerUser <= 0) {\n throw new Error(\n 'auth.experimentalRefreshToken.maxTokensPerUser must be a positive number',\n );\n }\n\n const knex = await database.getClient();\n\n if (\n knex.client.config.client.includes('sqlite') ||\n knex.client.config.client.includes('better-sqlite')\n ) {\n logger.warn(\n 'Refresh tokens are enabled with SQLite, which does not support row-level locking. ' +\n 'Concurrent token rotation may not be fully protected against race conditions. ' +\n 'Use PostgreSQL for production deployments.',\n );\n }\n\n const offlineSessionDb = OfflineSessionDatabase.create({\n knex,\n tokenLifetimeSeconds,\n maxRotationLifetimeSeconds,\n maxTokensPerUser,\n });\n\n const cleanupIntervalMs = 60 * 60 * 1000;\n const cleanupInterval = setInterval(async () => {\n try {\n const deleted = await offlineSessionDb.cleanupExpiredSessions();\n if (deleted > 0) {\n logger.info(`Cleaned up ${deleted} expired offline sessions`);\n }\n } catch (error) {\n logger.error('Failed to cleanup expired offline sessions', error);\n }\n }, cleanupIntervalMs);\n cleanupInterval.unref();\n\n lifecycle.addShutdownHook(() => {\n clearInterval(cleanupInterval);\n });\n\n return new OfflineAccessService(offlineSessionDb, logger);\n }\n\n private constructor(\n offlineSessionDb: OfflineSessionDatabase,\n logger: LoggerService,\n ) {\n this.#offlineSessionDb = offlineSessionDb;\n this.#logger = logger;\n }\n\n /**\n * Issue a new refresh token for a user\n */\n async issueRefreshToken(options: {\n userEntityRef: string;\n oidcClientId?: string;\n }): Promise<string> {\n const { userEntityRef, oidcClientId } = options;\n\n const sessionId = uuid();\n const { token, hash } = await generateRefreshToken(sessionId);\n\n await this.#offlineSessionDb.createSession({\n id: sessionId,\n userEntityRef,\n oidcClientId,\n tokenHash: hash,\n });\n\n this.#logger.debug(\n `Issued refresh token for user ${userEntityRef} with session ${sessionId}`,\n );\n\n return token;\n }\n\n /**\n * Refresh an access token using a refresh token\n */\n async refreshAccessToken(options: {\n refreshToken: string;\n tokenIssuer: TokenIssuer;\n clientId?: string;\n }): Promise<{ accessToken: string; refreshToken: string }> {\n const { refreshToken, tokenIssuer, clientId } = options;\n\n let sessionId: string;\n try {\n sessionId = getRefreshTokenId(refreshToken);\n } catch (error) {\n this.#logger.debug('Failed to extract refresh token ID', error);\n throw new AuthenticationError('Invalid refresh token format');\n }\n\n const session = await this.#offlineSessionDb.getSessionById(sessionId);\n if (!session) {\n throw new AuthenticationError('Invalid refresh token');\n }\n\n if (this.#offlineSessionDb.isSessionExpired(session)) {\n await this.#offlineSessionDb.deleteSession(sessionId);\n throw new AuthenticationError('Invalid refresh token');\n }\n\n if (clientId && session.oidcClientId && clientId !== session.oidcClientId) {\n throw new AuthenticationError(\n 'Refresh token was not issued to this client',\n );\n }\n\n // Verify the caller actually holds a valid token, not just the session ID\n const isValid = await verifyRefreshToken(refreshToken, session.tokenHash);\n if (!isValid) {\n throw new AuthenticationError('Invalid refresh token');\n }\n\n const { token: newRefreshToken, hash: newHash } =\n await generateRefreshToken(sessionId);\n\n // Atomically swap the hash so a concurrent request with the same token fails\n const rotatedSession = await this.#offlineSessionDb.getAndRotateToken(\n sessionId,\n session.tokenHash,\n newHash,\n );\n\n if (!rotatedSession) {\n throw new AuthenticationError('Invalid refresh token');\n }\n\n const { token: accessToken } = await tokenIssuer.issueToken({\n claims: {\n sub: rotatedSession.userEntityRef,\n },\n });\n\n this.#logger.debug(\n `Refreshed access token for user ${session.userEntityRef} with session ${sessionId}`,\n );\n\n return { accessToken, refreshToken: newRefreshToken };\n }\n\n /**\n * Revoke a refresh token\n */\n async revokeRefreshToken(refreshToken: string): Promise<void> {\n try {\n const sessionId = getRefreshTokenId(refreshToken);\n await this.#offlineSessionDb.deleteSession(sessionId);\n this.#logger.debug(`Revoked refresh token with session ${sessionId}`);\n } catch (error) {\n // Ignore errors when revoking - token may already be invalid\n this.#logger.debug('Failed to revoke refresh token', error);\n }\n }\n\n /**\n * Revoke all refresh tokens for a user\n */\n async revokeRefreshTokensByUserEntityRef(\n userEntityRef: string,\n ): Promise<void> {\n const deletedCount =\n await this.#offlineSessionDb.deleteSessionsByUserEntityRef(userEntityRef);\n this.#logger.debug(\n `Revoked ${deletedCount} refresh tokens for user ${userEntityRef}`,\n );\n }\n}\n"],"names":["config","readDurationFromConfig","durationToMilliseconds","OfflineSessionDatabase","uuid","generateRefreshToken","refreshToken","getRefreshTokenId","AuthenticationError","verifyRefreshToken"],"mappings":"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"}
1
+ {"version":3,"file":"OfflineAccessService.cjs.js","sources":["../../src/service/OfflineAccessService.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n DatabaseService,\n LifecycleService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { AuthenticationError } from '@backstage/errors';\nimport { readDurationFromConfig } from '@backstage/config';\nimport { durationToMilliseconds } from '@backstage/types';\nimport { randomUUID as uuid } from 'node:crypto';\nimport { OfflineSessionDatabase } from '../database/OfflineSessionDatabase';\nimport {\n generateRefreshToken,\n getRefreshTokenId,\n verifyRefreshToken,\n} from '../lib/refreshToken';\nimport { TokenIssuer } from '../identity/types';\n\n/**\n * Service for managing offline access (refresh tokens)\n * @internal\n */\nexport class OfflineAccessService {\n readonly #offlineSessionDb: OfflineSessionDatabase;\n readonly #logger: LoggerService;\n\n static async create(options: {\n config: RootConfigService;\n database: DatabaseService;\n logger: LoggerService;\n lifecycle: LifecycleService;\n }): Promise<OfflineAccessService> {\n const { config, database, logger, lifecycle } = options;\n\n const tokenLifetime = config.has(\n 'auth.experimentalRefreshToken.tokenLifetime',\n )\n ? readDurationFromConfig(config, {\n key: 'auth.experimentalRefreshToken.tokenLifetime',\n })\n : { days: 30 };\n\n const maxRotationLifetime = config.has(\n 'auth.experimentalRefreshToken.maxRotationLifetime',\n )\n ? readDurationFromConfig(config, {\n key: 'auth.experimentalRefreshToken.maxRotationLifetime',\n })\n : { years: 1 };\n\n const tokenLifetimeSeconds = Math.floor(\n durationToMilliseconds(tokenLifetime) / 1000,\n );\n const maxRotationLifetimeSeconds = Math.floor(\n durationToMilliseconds(maxRotationLifetime) / 1000,\n );\n\n if (tokenLifetimeSeconds <= 0) {\n throw new Error(\n 'auth.experimentalRefreshToken.tokenLifetime must be a positive duration',\n );\n }\n if (maxRotationLifetimeSeconds <= 0) {\n throw new Error(\n 'auth.experimentalRefreshToken.maxRotationLifetime must be a positive duration',\n );\n }\n if (maxRotationLifetimeSeconds <= tokenLifetimeSeconds) {\n throw new Error(\n 'auth.experimentalRefreshToken.maxRotationLifetime must be greater than tokenLifetime',\n );\n }\n\n const maxTokensPerUser =\n config.getOptionalNumber(\n 'auth.experimentalRefreshToken.maxTokensPerUser',\n ) ?? 20;\n\n if (maxTokensPerUser <= 0) {\n throw new Error(\n 'auth.experimentalRefreshToken.maxTokensPerUser must be a positive number',\n );\n }\n\n const knex = await database.getClient();\n\n if (\n knex.client.config.client.includes('sqlite') ||\n knex.client.config.client.includes('better-sqlite')\n ) {\n logger.warn(\n 'Refresh tokens are enabled with SQLite, which does not support row-level locking. ' +\n 'Concurrent token rotation may not be fully protected against race conditions. ' +\n 'Use PostgreSQL for production deployments.',\n );\n }\n\n const offlineSessionDb = OfflineSessionDatabase.create({\n knex,\n tokenLifetimeSeconds,\n maxRotationLifetimeSeconds,\n maxTokensPerUser,\n });\n\n const cleanupIntervalMs = 60 * 60 * 1000;\n const cleanupInterval = setInterval(async () => {\n try {\n const deleted = await offlineSessionDb.cleanupExpiredSessions();\n if (deleted > 0) {\n logger.info(`Cleaned up ${deleted} expired offline sessions`);\n }\n } catch (error) {\n logger.error('Failed to cleanup expired offline sessions', error);\n }\n }, cleanupIntervalMs);\n cleanupInterval.unref();\n\n lifecycle.addShutdownHook(() => {\n clearInterval(cleanupInterval);\n });\n\n return new OfflineAccessService(offlineSessionDb, logger);\n }\n\n private constructor(\n offlineSessionDb: OfflineSessionDatabase,\n logger: LoggerService,\n ) {\n this.#offlineSessionDb = offlineSessionDb;\n this.#logger = logger;\n }\n\n /**\n * Issue a new refresh token for a user\n */\n async issueRefreshToken(options: {\n userEntityRef: string;\n oidcClientId?: string;\n }): Promise<string> {\n const { userEntityRef, oidcClientId } = options;\n\n const sessionId = uuid();\n const { token, hash } = await generateRefreshToken(sessionId);\n\n await this.#offlineSessionDb.createSession({\n id: sessionId,\n userEntityRef,\n oidcClientId,\n tokenHash: hash,\n });\n\n this.#logger.debug(\n `Issued refresh token for user ${userEntityRef} with session ${sessionId}`,\n );\n\n return token;\n }\n\n /**\n * Refresh an access token using a refresh token\n */\n async refreshAccessToken(options: {\n refreshToken: string;\n tokenIssuer: TokenIssuer;\n clientId?: string;\n }): Promise<{ accessToken: string; refreshToken: string }> {\n const { refreshToken, tokenIssuer, clientId } = options;\n\n let sessionId: string;\n try {\n sessionId = getRefreshTokenId(refreshToken);\n } catch (error) {\n this.#logger.debug('Failed to extract refresh token ID', error);\n throw new AuthenticationError('Invalid refresh token format');\n }\n\n const session = await this.#offlineSessionDb.getSessionById(sessionId);\n if (!session) {\n throw new AuthenticationError('Invalid refresh token');\n }\n\n if (this.#offlineSessionDb.isSessionExpired(session)) {\n await this.#offlineSessionDb.deleteSession(sessionId);\n throw new AuthenticationError('Invalid refresh token');\n }\n\n if (clientId && session.oidcClientId && clientId !== session.oidcClientId) {\n throw new AuthenticationError(\n 'Refresh token was not issued to this client',\n );\n }\n\n // Verify the caller actually holds a valid token, not just the session ID\n const isValid = await verifyRefreshToken(refreshToken, session.tokenHash);\n if (!isValid) {\n throw new AuthenticationError('Invalid refresh token');\n }\n\n const { token: newRefreshToken, hash: newHash } =\n await generateRefreshToken(sessionId);\n\n // Atomically swap the hash so a concurrent request with the same token fails\n const rotatedSession = await this.#offlineSessionDb.getAndRotateToken(\n sessionId,\n session.tokenHash,\n newHash,\n );\n\n if (!rotatedSession) {\n throw new AuthenticationError('Invalid refresh token');\n }\n\n const { token: accessToken } = await tokenIssuer.issueToken({\n claims: {\n sub: rotatedSession.userEntityRef,\n },\n });\n\n this.#logger.debug(\n `Refreshed access token for user ${session.userEntityRef} with session ${sessionId}`,\n );\n\n return { accessToken, refreshToken: newRefreshToken };\n }\n\n /**\n * Revoke a refresh token\n */\n async revokeRefreshToken(refreshToken: string): Promise<void> {\n try {\n const sessionId = getRefreshTokenId(refreshToken);\n await this.#offlineSessionDb.deleteSession(sessionId);\n this.#logger.debug(`Revoked refresh token with session ${sessionId}`);\n } catch (error) {\n // Ignore errors when revoking - token may already be invalid\n this.#logger.debug('Failed to revoke refresh token', error);\n }\n }\n\n /**\n * Revoke all refresh tokens for a user\n */\n async revokeRefreshTokensByUserEntityRef(\n userEntityRef: string,\n ): Promise<void> {\n const deletedCount =\n await this.#offlineSessionDb.deleteSessionsByUserEntityRef(userEntityRef);\n this.#logger.debug(\n `Revoked ${deletedCount} refresh tokens for user ${userEntityRef}`,\n );\n }\n}\n"],"names":["config","readDurationFromConfig","durationToMilliseconds","OfflineSessionDatabase","uuid","generateRefreshToken","refreshToken","getRefreshTokenId","AuthenticationError","verifyRefreshToken"],"mappings":"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"}
@@ -232,6 +232,7 @@ class OidcRouter {
232
232
  });
233
233
  return res.json({
234
234
  id: session.id,
235
+ clientId: session.clientId,
235
236
  clientName: session.clientName,
236
237
  scope: session.scope,
237
238
  redirectUri: session.redirectUri
@@ -1 +1 @@
1
- {"version":3,"file":"OidcRouter.cjs.js","sources":["../../src/service/OidcRouter.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport Router from 'express-promise-router';\nimport { OidcService } from './OidcService';\nimport { AuthenticationError, isError } from '@backstage/errors';\nimport {\n AuthService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { OfflineAccessService } from './OfflineAccessService';\nimport { json } from 'express';\nimport { z } from 'zod/v4';\nimport { fromZodError } from 'zod-validation-error/v4';\nimport { OidcError } from './OidcError';\n\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith('/') ? url : `${url}/`;\n}\n\nconst authorizeQuerySchema = z.object({\n client_id: z.string().min(1),\n redirect_uri: z.string().url(),\n response_type: z.string().min(1),\n scope: z.string().optional(),\n state: z.string().optional(),\n nonce: z.string().optional(),\n code_challenge: z.string().optional(),\n code_challenge_method: z.string().optional(),\n});\n\nconst sessionIdParamSchema = z.object({\n sessionId: z.string().min(1),\n});\n\nconst tokenRequestBodySchema = z.object({\n grant_type: z.string().min(1),\n code: z.string().optional(),\n redirect_uri: z.string().url().optional(),\n code_verifier: z.string().optional(),\n refresh_token: z.string().optional(),\n client_id: z.string().optional(),\n client_secret: z.string().optional(),\n});\n\nconst registerRequestBodySchema = z.object({\n client_name: z.string().optional(),\n redirect_uris: z.array(z.string().url()).min(1),\n response_types: z.array(z.string()).optional(),\n grant_types: z.array(z.string()).optional(),\n scope: z.string().optional(),\n});\n\nconst revokeRequestBodySchema = z.object({\n token: z.string().min(1),\n token_type_hint: z.string().optional(),\n client_id: z.string().optional(),\n client_secret: z.string().optional(),\n});\n\nfunction validateRequest<T>(schema: z.ZodSchema<T>, data: unknown): T {\n const parseResult = schema.safeParse(data);\n if (!parseResult.success) {\n const errorMessage = fromZodError(parseResult.error).message;\n throw new OidcError('invalid_request', errorMessage, 400);\n }\n return parseResult.data;\n}\n\nasync function authenticateClient(opts: {\n req: { headers: { authorization?: string } };\n oidc: OidcService;\n bodyClientId?: string;\n bodyClientSecret?: string;\n}): Promise<{ clientId: string; clientSecret: string }> {\n const { req, oidc, bodyClientId, bodyClientSecret } = opts;\n let clientId: string | undefined;\n let clientSecret: string | undefined;\n\n const basicAuth = req.headers.authorization?.match(/^Basic[ ]+([^\\s]+)$/i);\n if (basicAuth) {\n try {\n const decoded = Buffer.from(basicAuth[1], 'base64').toString('utf8');\n const idx = decoded.indexOf(':');\n if (idx >= 0) {\n clientId = decoded.slice(0, idx);\n clientSecret = decoded.slice(idx + 1);\n }\n } catch {\n /* ignore */\n }\n }\n\n if (!clientId || !clientSecret) {\n if (bodyClientId && bodyClientSecret) {\n clientId = bodyClientId;\n clientSecret = bodyClientSecret;\n }\n }\n\n if (!clientId || !clientSecret) {\n throw new OidcError(\n 'invalid_client',\n 'Client authentication required',\n 401,\n );\n }\n\n try {\n const ok = await oidc.verifyClientCredentials({\n clientId,\n clientSecret,\n });\n if (!ok) {\n throw new OidcError('invalid_client', 'Invalid client credentials', 401);\n }\n } catch (e) {\n throw OidcError.fromError(e);\n }\n\n return { clientId, clientSecret };\n}\n\nexport class OidcRouter {\n private readonly oidc: OidcService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n private readonly appUrl: string;\n private readonly httpAuth: HttpAuthService;\n private readonly config: RootConfigService;\n private readonly baseUrl: string;\n\n private constructor(\n oidc: OidcService,\n logger: LoggerService,\n auth: AuthService,\n appUrl: string,\n httpAuth: HttpAuthService,\n config: RootConfigService,\n baseUrl: string,\n ) {\n this.oidc = oidc;\n this.logger = logger;\n this.auth = auth;\n this.appUrl = appUrl;\n this.httpAuth = httpAuth;\n this.config = config;\n this.baseUrl = baseUrl;\n }\n\n static create(options: {\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n baseUrl: string;\n appUrl: string;\n logger: LoggerService;\n userInfo: UserInfoDatabase;\n oidc: OidcDatabase;\n httpAuth: HttpAuthService;\n config: RootConfigService;\n offlineAccess?: OfflineAccessService;\n }) {\n return new OidcRouter(\n OidcService.create(options),\n options.logger,\n options.auth,\n options.appUrl,\n options.httpAuth,\n options.config,\n options.baseUrl,\n );\n }\n\n public getRouter() {\n const router = Router();\n\n router.use(json());\n\n // OpenID Provider Configuration endpoint\n // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig\n // Returns the OpenID Provider Configuration document containing metadata about the provider\n router.get('/.well-known/openid-configuration', (_req, res) => {\n res.json(this.oidc.getConfiguration());\n });\n\n // JSON Web Key Set endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.10.1.1\n // Returns the public keys used to verify JWTs issued by this provider\n router.get('/.well-known/jwks.json', async (_req, res) => {\n const { keys } = await this.oidc.listPublicKeys();\n res.json({ keys });\n });\n\n // CIMD metadata endpoint for the Backstage CLI\n // Automatically available when CIMD is enabled\n router.get('/.well-known/oauth-client/cli.json', (_req, res) => {\n const cimdEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalClientIdMetadataDocuments.enabled',\n );\n\n if (!cimdEnabled) {\n res.status(404).json({\n error: 'not_found',\n error_description: 'Client ID metadata documents not enabled',\n });\n return;\n }\n\n res.json({\n client_id: `${this.baseUrl}/.well-known/oauth-client/cli.json`,\n client_name: 'Backstage CLI',\n redirect_uris: ['http://127.0.0.1:8055/callback'],\n response_types: ['code'],\n grant_types: ['authorization_code'],\n token_endpoint_auth_method: 'none',\n scope: 'openid offline_access',\n });\n });\n\n // UserInfo endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#UserInfo\n // Returns claims about the authenticated user using an access token\n router.get('/v1/userinfo', async (req, res) => {\n const matches = req.headers.authorization?.match(/^Bearer[ ]+(\\S+)$/i);\n const token = matches?.[1];\n if (!token) {\n throw new AuthenticationError('No token provided');\n }\n\n const userInfo = await this.oidc.getUserInfo({ token });\n\n if (!userInfo) {\n res.status(404).send('User info not found');\n return;\n }\n\n res.json(userInfo);\n });\n\n const dcrEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n );\n const cimdEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalClientIdMetadataDocuments.enabled',\n );\n\n if (dcrEnabled || cimdEnabled) {\n // Authorization endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest\n // Handles the initial authorization request from the client, validates parameters,\n // and redirects to the Authorization Session page for user approval\n router.get('/v1/authorize', async (req, res) => {\n const {\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: responseType,\n scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: codeChallengeMethod,\n } = validateRequest(authorizeQuerySchema, req.query);\n\n try {\n const result = await this.oidc.createAuthorizationSession({\n clientId,\n redirectUri,\n responseType,\n scope,\n state,\n nonce,\n codeChallenge,\n codeChallengeMethod,\n });\n\n // todo(blam): maybe this URL could be overridable by config if\n // the plugin is mounted somewhere else?\n // support slashes in baseUrl?\n const authSessionRedirectUrl = new URL(\n `./oauth2/authorize/${result.id}`,\n ensureTrailingSlash(this.appUrl),\n );\n\n return res.redirect(authSessionRedirectUrl.toString());\n } catch (error) {\n if (OidcError.isOidcError(error)) {\n const errorParams = new URLSearchParams();\n errorParams.append('error', error.body.error);\n errorParams.append(\n 'error_description',\n error.body.error_description,\n );\n if (state) {\n errorParams.append('state', state);\n }\n\n const redirectUrl = new URL(redirectUri);\n redirectUrl.search = errorParams.toString();\n return res.redirect(redirectUrl.toString());\n }\n throw error;\n }\n });\n\n // Authorization Session request details endpoint\n // Returns Authorization Session request details for the frontend\n router.get('/v1/sessions/:sessionId', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n return res.json({\n id: session.id,\n clientName: session.clientName,\n scope: session.scope,\n redirectUri: session.redirectUri,\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Authorization Session approval endpoint\n // Handles user approval of Authorization Session requests and generates authorization codes\n router.post('/v1/sessions/:sessionId/approve', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n try {\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n throw new OidcError(\n 'access_denied',\n 'Authentication required',\n 403,\n );\n }\n\n const { userEntityRef } = httpCredentials.principal;\n\n const result = await this.oidc.approveAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n return res.json({\n redirectUrl: result.redirectUrl,\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Authorization Session rejection endpoint\n // Handles user rejection of Authorization Session requests and redirects with error\n router.post('/v1/sessions/:sessionId/reject', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n throw new OidcError('access_denied', 'Authentication required', 403);\n }\n\n const { userEntityRef } = httpCredentials.principal;\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n await this.oidc.rejectAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n const errorParams = new URLSearchParams();\n errorParams.append('error', 'access_denied');\n errorParams.append('error_description', 'User denied the request');\n if (session.state) {\n errorParams.append('state', session.state);\n }\n\n const redirectUrl = new URL(session.redirectUri);\n redirectUrl.search = errorParams.toString();\n\n return res.json({\n redirectUrl: redirectUrl.toString(),\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Token endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest\n // Exchanges authorization codes for access tokens and ID tokens\n // Also handles refresh token grant type\n router.post('/v1/token', async (req, res) => {\n const {\n grant_type: grantType,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n refresh_token: refreshToken,\n client_id: bodyClientId,\n client_secret: bodyClientSecret,\n } = validateRequest(tokenRequestBodySchema, req.body);\n\n try {\n // Handle authorization_code grant type\n if (grantType === 'authorization_code') {\n if (!code || !redirectUri) {\n throw new OidcError(\n 'invalid_request',\n 'Missing code or redirect_uri parameters for authorization_code grant',\n 400,\n );\n }\n\n const result = await this.oidc.exchangeCodeForToken({\n code,\n redirectUri,\n codeVerifier,\n grantType,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n id_token: result.idToken,\n scope: result.scope,\n ...(result.refreshToken && {\n refresh_token: result.refreshToken,\n }),\n });\n }\n\n // Handle refresh_token grant type\n if (grantType === 'refresh_token') {\n if (!refreshToken) {\n throw new OidcError(\n 'invalid_request',\n 'Missing refresh_token parameter for refresh_token grant',\n 400,\n );\n }\n\n // Authenticate if credentials are provided via Basic auth or body\n const hasCredentials =\n req.headers.authorization?.match(/^Basic[ ]+([^\\s]+)$/i) ||\n (bodyClientId && bodyClientSecret);\n\n let authenticatedClientId: string | undefined;\n if (hasCredentials) {\n const { clientId: authedId } = await authenticateClient({\n req,\n oidc: this.oidc,\n bodyClientId,\n bodyClientSecret,\n });\n authenticatedClientId = authedId;\n }\n\n const result = await this.oidc.refreshAccessToken({\n refreshToken,\n clientId: authenticatedClientId,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n refresh_token: result.refreshToken,\n });\n }\n\n // Unsupported grant type\n throw new OidcError(\n 'unsupported_grant_type',\n `Grant type ${grantType} is not supported`,\n 400,\n );\n } catch (error) {\n // Invalid auth codes and refresh tokens should be invalid_grant, not invalid_client.\n // Client auth failures are already thrown as OidcError by authenticateClient.\n if (isError(error) && error.name === 'AuthenticationError') {\n throw new OidcError('invalid_grant', error.message, 400, error);\n }\n throw OidcError.fromError(error);\n }\n });\n }\n\n // Dynamic Client Registration endpoint - only available when DCR is enabled\n if (dcrEnabled) {\n // https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration\n // Allows clients to register themselves dynamically with the provider\n router.post('/v1/register', async (req, res) => {\n const {\n client_name: clientName,\n redirect_uris: redirectUris,\n response_types: responseTypes,\n grant_types: grantTypes,\n scope,\n } = validateRequest(registerRequestBodySchema, req.body);\n\n try {\n const client = await this.oidc.registerClient({\n clientName: clientName ?? 'Backstage CLI',\n redirectUris,\n responseTypes,\n grantTypes,\n scope,\n });\n\n return res.status(201).json({\n client_id: client.clientId,\n redirect_uris: client.redirectUris,\n client_secret: client.clientSecret,\n });\n } catch (e) {\n throw OidcError.fromError(e);\n }\n });\n\n // Token Revocation endpoint (RFC 7009-like)\n // Allows clients to revoke refresh tokens\n router.post('/v1/revoke', async (req, res) => {\n try {\n const {\n token,\n client_id: bodyClientId,\n client_secret: bodyClientSecret,\n } = validateRequest(revokeRequestBodySchema, req.body ?? {});\n\n await authenticateClient({\n req,\n oidc: this.oidc,\n bodyClientId,\n bodyClientSecret,\n });\n\n try {\n await this.oidc.revokeRefreshToken(token);\n } catch (e) {\n // RFC 7009 says always respond 200 even for invalid tokens\n this.logger.debug('Failed to revoke token', e);\n }\n\n return res.status(200).send('');\n } catch (e) {\n throw OidcError.fromError(e);\n }\n });\n }\n\n router.use(OidcError.middleware(this.logger));\n\n return router;\n }\n}\n"],"names":["z","fromZodError","OidcError","OidcService","Router","json","cimdEnabled","AuthenticationError","isError"],"mappings":"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"}
1
+ {"version":3,"file":"OidcRouter.cjs.js","sources":["../../src/service/OidcRouter.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport Router from 'express-promise-router';\nimport { OidcService } from './OidcService';\nimport { AuthenticationError, isError } from '@backstage/errors';\nimport {\n AuthService,\n HttpAuthService,\n LoggerService,\n RootConfigService,\n} from '@backstage/backend-plugin-api';\nimport { TokenIssuer } from '../identity/types';\nimport { UserInfoDatabase } from '../database/UserInfoDatabase';\nimport { OidcDatabase } from '../database/OidcDatabase';\nimport { OfflineAccessService } from './OfflineAccessService';\nimport { json } from 'express';\nimport { z } from 'zod/v4';\nimport { fromZodError } from 'zod-validation-error/v4';\nimport { OidcError } from './OidcError';\n\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith('/') ? url : `${url}/`;\n}\n\nconst authorizeQuerySchema = z.object({\n client_id: z.string().min(1),\n redirect_uri: z.string().url(),\n response_type: z.string().min(1),\n scope: z.string().optional(),\n state: z.string().optional(),\n nonce: z.string().optional(),\n code_challenge: z.string().optional(),\n code_challenge_method: z.string().optional(),\n});\n\nconst sessionIdParamSchema = z.object({\n sessionId: z.string().min(1),\n});\n\nconst tokenRequestBodySchema = z.object({\n grant_type: z.string().min(1),\n code: z.string().optional(),\n redirect_uri: z.string().url().optional(),\n code_verifier: z.string().optional(),\n refresh_token: z.string().optional(),\n client_id: z.string().optional(),\n client_secret: z.string().optional(),\n});\n\nconst registerRequestBodySchema = z.object({\n client_name: z.string().optional(),\n redirect_uris: z.array(z.string().url()).min(1),\n response_types: z.array(z.string()).optional(),\n grant_types: z.array(z.string()).optional(),\n scope: z.string().optional(),\n});\n\nconst revokeRequestBodySchema = z.object({\n token: z.string().min(1),\n token_type_hint: z.string().optional(),\n client_id: z.string().optional(),\n client_secret: z.string().optional(),\n});\n\nfunction validateRequest<T>(schema: z.ZodSchema<T>, data: unknown): T {\n const parseResult = schema.safeParse(data);\n if (!parseResult.success) {\n const errorMessage = fromZodError(parseResult.error).message;\n throw new OidcError('invalid_request', errorMessage, 400);\n }\n return parseResult.data;\n}\n\nasync function authenticateClient(opts: {\n req: { headers: { authorization?: string } };\n oidc: OidcService;\n bodyClientId?: string;\n bodyClientSecret?: string;\n}): Promise<{ clientId: string; clientSecret: string }> {\n const { req, oidc, bodyClientId, bodyClientSecret } = opts;\n let clientId: string | undefined;\n let clientSecret: string | undefined;\n\n const basicAuth = req.headers.authorization?.match(/^Basic[ ]+([^\\s]+)$/i);\n if (basicAuth) {\n try {\n const decoded = Buffer.from(basicAuth[1], 'base64').toString('utf8');\n const idx = decoded.indexOf(':');\n if (idx >= 0) {\n clientId = decoded.slice(0, idx);\n clientSecret = decoded.slice(idx + 1);\n }\n } catch {\n /* ignore */\n }\n }\n\n if (!clientId || !clientSecret) {\n if (bodyClientId && bodyClientSecret) {\n clientId = bodyClientId;\n clientSecret = bodyClientSecret;\n }\n }\n\n if (!clientId || !clientSecret) {\n throw new OidcError(\n 'invalid_client',\n 'Client authentication required',\n 401,\n );\n }\n\n try {\n const ok = await oidc.verifyClientCredentials({\n clientId,\n clientSecret,\n });\n if (!ok) {\n throw new OidcError('invalid_client', 'Invalid client credentials', 401);\n }\n } catch (e) {\n throw OidcError.fromError(e);\n }\n\n return { clientId, clientSecret };\n}\n\nexport class OidcRouter {\n private readonly oidc: OidcService;\n private readonly logger: LoggerService;\n private readonly auth: AuthService;\n private readonly appUrl: string;\n private readonly httpAuth: HttpAuthService;\n private readonly config: RootConfigService;\n private readonly baseUrl: string;\n\n private constructor(\n oidc: OidcService,\n logger: LoggerService,\n auth: AuthService,\n appUrl: string,\n httpAuth: HttpAuthService,\n config: RootConfigService,\n baseUrl: string,\n ) {\n this.oidc = oidc;\n this.logger = logger;\n this.auth = auth;\n this.appUrl = appUrl;\n this.httpAuth = httpAuth;\n this.config = config;\n this.baseUrl = baseUrl;\n }\n\n static create(options: {\n auth: AuthService;\n tokenIssuer: TokenIssuer;\n baseUrl: string;\n appUrl: string;\n logger: LoggerService;\n userInfo: UserInfoDatabase;\n oidc: OidcDatabase;\n httpAuth: HttpAuthService;\n config: RootConfigService;\n offlineAccess?: OfflineAccessService;\n }) {\n return new OidcRouter(\n OidcService.create(options),\n options.logger,\n options.auth,\n options.appUrl,\n options.httpAuth,\n options.config,\n options.baseUrl,\n );\n }\n\n public getRouter() {\n const router = Router();\n\n router.use(json());\n\n // OpenID Provider Configuration endpoint\n // https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig\n // Returns the OpenID Provider Configuration document containing metadata about the provider\n router.get('/.well-known/openid-configuration', (_req, res) => {\n res.json(this.oidc.getConfiguration());\n });\n\n // JSON Web Key Set endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.10.1.1\n // Returns the public keys used to verify JWTs issued by this provider\n router.get('/.well-known/jwks.json', async (_req, res) => {\n const { keys } = await this.oidc.listPublicKeys();\n res.json({ keys });\n });\n\n // CIMD metadata endpoint for the Backstage CLI\n // Automatically available when CIMD is enabled\n router.get('/.well-known/oauth-client/cli.json', (_req, res) => {\n const cimdEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalClientIdMetadataDocuments.enabled',\n );\n\n if (!cimdEnabled) {\n res.status(404).json({\n error: 'not_found',\n error_description: 'Client ID metadata documents not enabled',\n });\n return;\n }\n\n res.json({\n client_id: `${this.baseUrl}/.well-known/oauth-client/cli.json`,\n client_name: 'Backstage CLI',\n redirect_uris: ['http://127.0.0.1:8055/callback'],\n response_types: ['code'],\n grant_types: ['authorization_code'],\n token_endpoint_auth_method: 'none',\n scope: 'openid offline_access',\n });\n });\n\n // UserInfo endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#UserInfo\n // Returns claims about the authenticated user using an access token\n router.get('/v1/userinfo', async (req, res) => {\n const matches = req.headers.authorization?.match(/^Bearer[ ]+(\\S+)$/i);\n const token = matches?.[1];\n if (!token) {\n throw new AuthenticationError('No token provided');\n }\n\n const userInfo = await this.oidc.getUserInfo({ token });\n\n if (!userInfo) {\n res.status(404).send('User info not found');\n return;\n }\n\n res.json(userInfo);\n });\n\n const dcrEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalDynamicClientRegistration.enabled',\n );\n const cimdEnabled = this.config.getOptionalBoolean(\n 'auth.experimentalClientIdMetadataDocuments.enabled',\n );\n\n if (dcrEnabled || cimdEnabled) {\n // Authorization endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest\n // Handles the initial authorization request from the client, validates parameters,\n // and redirects to the Authorization Session page for user approval\n router.get('/v1/authorize', async (req, res) => {\n const {\n client_id: clientId,\n redirect_uri: redirectUri,\n response_type: responseType,\n scope,\n state,\n nonce,\n code_challenge: codeChallenge,\n code_challenge_method: codeChallengeMethod,\n } = validateRequest(authorizeQuerySchema, req.query);\n\n try {\n const result = await this.oidc.createAuthorizationSession({\n clientId,\n redirectUri,\n responseType,\n scope,\n state,\n nonce,\n codeChallenge,\n codeChallengeMethod,\n });\n\n // todo(blam): maybe this URL could be overridable by config if\n // the plugin is mounted somewhere else?\n // support slashes in baseUrl?\n const authSessionRedirectUrl = new URL(\n `./oauth2/authorize/${result.id}`,\n ensureTrailingSlash(this.appUrl),\n );\n\n return res.redirect(authSessionRedirectUrl.toString());\n } catch (error) {\n if (OidcError.isOidcError(error)) {\n const errorParams = new URLSearchParams();\n errorParams.append('error', error.body.error);\n errorParams.append(\n 'error_description',\n error.body.error_description,\n );\n if (state) {\n errorParams.append('state', state);\n }\n\n const redirectUrl = new URL(redirectUri);\n redirectUrl.search = errorParams.toString();\n return res.redirect(redirectUrl.toString());\n }\n throw error;\n }\n });\n\n // Authorization Session request details endpoint\n // Returns Authorization Session request details for the frontend\n router.get('/v1/sessions/:sessionId', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n return res.json({\n id: session.id,\n clientId: session.clientId,\n clientName: session.clientName,\n scope: session.scope,\n redirectUri: session.redirectUri,\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Authorization Session approval endpoint\n // Handles user approval of Authorization Session requests and generates authorization codes\n router.post('/v1/sessions/:sessionId/approve', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n try {\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n throw new OidcError(\n 'access_denied',\n 'Authentication required',\n 403,\n );\n }\n\n const { userEntityRef } = httpCredentials.principal;\n\n const result = await this.oidc.approveAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n return res.json({\n redirectUrl: result.redirectUrl,\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Authorization Session rejection endpoint\n // Handles user rejection of Authorization Session requests and redirects with error\n router.post('/v1/sessions/:sessionId/reject', async (req, res) => {\n const { sessionId } = validateRequest(sessionIdParamSchema, req.params);\n\n const httpCredentials = await this.httpAuth.credentials(req);\n\n if (!this.auth.isPrincipal(httpCredentials, 'user')) {\n throw new OidcError('access_denied', 'Authentication required', 403);\n }\n\n const { userEntityRef } = httpCredentials.principal;\n try {\n const session = await this.oidc.getAuthorizationSession({\n sessionId,\n });\n\n await this.oidc.rejectAuthorizationSession({\n sessionId,\n userEntityRef,\n });\n\n const errorParams = new URLSearchParams();\n errorParams.append('error', 'access_denied');\n errorParams.append('error_description', 'User denied the request');\n if (session.state) {\n errorParams.append('state', session.state);\n }\n\n const redirectUrl = new URL(session.redirectUri);\n redirectUrl.search = errorParams.toString();\n\n return res.json({\n redirectUrl: redirectUrl.toString(),\n });\n } catch (error) {\n throw OidcError.fromError(error);\n }\n });\n\n // Token endpoint\n // https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest\n // Exchanges authorization codes for access tokens and ID tokens\n // Also handles refresh token grant type\n router.post('/v1/token', async (req, res) => {\n const {\n grant_type: grantType,\n code,\n redirect_uri: redirectUri,\n code_verifier: codeVerifier,\n refresh_token: refreshToken,\n client_id: bodyClientId,\n client_secret: bodyClientSecret,\n } = validateRequest(tokenRequestBodySchema, req.body);\n\n try {\n // Handle authorization_code grant type\n if (grantType === 'authorization_code') {\n if (!code || !redirectUri) {\n throw new OidcError(\n 'invalid_request',\n 'Missing code or redirect_uri parameters for authorization_code grant',\n 400,\n );\n }\n\n const result = await this.oidc.exchangeCodeForToken({\n code,\n redirectUri,\n codeVerifier,\n grantType,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n id_token: result.idToken,\n scope: result.scope,\n ...(result.refreshToken && {\n refresh_token: result.refreshToken,\n }),\n });\n }\n\n // Handle refresh_token grant type\n if (grantType === 'refresh_token') {\n if (!refreshToken) {\n throw new OidcError(\n 'invalid_request',\n 'Missing refresh_token parameter for refresh_token grant',\n 400,\n );\n }\n\n // Authenticate if credentials are provided via Basic auth or body\n const hasCredentials =\n req.headers.authorization?.match(/^Basic[ ]+([^\\s]+)$/i) ||\n (bodyClientId && bodyClientSecret);\n\n let authenticatedClientId: string | undefined;\n if (hasCredentials) {\n const { clientId: authedId } = await authenticateClient({\n req,\n oidc: this.oidc,\n bodyClientId,\n bodyClientSecret,\n });\n authenticatedClientId = authedId;\n }\n\n const result = await this.oidc.refreshAccessToken({\n refreshToken,\n clientId: authenticatedClientId,\n });\n\n return res.json({\n access_token: result.accessToken,\n token_type: result.tokenType,\n expires_in: result.expiresIn,\n refresh_token: result.refreshToken,\n });\n }\n\n // Unsupported grant type\n throw new OidcError(\n 'unsupported_grant_type',\n `Grant type ${grantType} is not supported`,\n 400,\n );\n } catch (error) {\n // Invalid auth codes and refresh tokens should be invalid_grant, not invalid_client.\n // Client auth failures are already thrown as OidcError by authenticateClient.\n if (isError(error) && error.name === 'AuthenticationError') {\n throw new OidcError('invalid_grant', error.message, 400, error);\n }\n throw OidcError.fromError(error);\n }\n });\n }\n\n // Dynamic Client Registration endpoint - only available when DCR is enabled\n if (dcrEnabled) {\n // https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration\n // Allows clients to register themselves dynamically with the provider\n router.post('/v1/register', async (req, res) => {\n const {\n client_name: clientName,\n redirect_uris: redirectUris,\n response_types: responseTypes,\n grant_types: grantTypes,\n scope,\n } = validateRequest(registerRequestBodySchema, req.body);\n\n try {\n const client = await this.oidc.registerClient({\n clientName: clientName ?? 'Backstage CLI',\n redirectUris,\n responseTypes,\n grantTypes,\n scope,\n });\n\n return res.status(201).json({\n client_id: client.clientId,\n redirect_uris: client.redirectUris,\n client_secret: client.clientSecret,\n });\n } catch (e) {\n throw OidcError.fromError(e);\n }\n });\n\n // Token Revocation endpoint (RFC 7009-like)\n // Allows clients to revoke refresh tokens\n router.post('/v1/revoke', async (req, res) => {\n try {\n const {\n token,\n client_id: bodyClientId,\n client_secret: bodyClientSecret,\n } = validateRequest(revokeRequestBodySchema, req.body ?? {});\n\n await authenticateClient({\n req,\n oidc: this.oidc,\n bodyClientId,\n bodyClientSecret,\n });\n\n try {\n await this.oidc.revokeRefreshToken(token);\n } catch (e) {\n // RFC 7009 says always respond 200 even for invalid tokens\n this.logger.debug('Failed to revoke token', e);\n }\n\n return res.status(200).send('');\n } catch (e) {\n throw OidcError.fromError(e);\n }\n });\n }\n\n router.use(OidcError.middleware(this.logger));\n\n return router;\n }\n}\n"],"names":["z","fromZodError","OidcError","OidcService","Router","json","cimdEnabled","AuthenticationError","isError"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/plugin-auth-backend",
3
- "version": "0.28.1-next.0",
3
+ "version": "0.28.1-next.2",
4
4
  "description": "A Backstage backend plugin that handles authentication",
5
5
  "backstage": {
6
6
  "role": "backend-plugin",
@@ -47,12 +47,12 @@
47
47
  "test": "backstage-cli package test"
48
48
  },
49
49
  "dependencies": {
50
- "@backstage/backend-plugin-api": "1.9.1-next.0",
51
- "@backstage/catalog-model": "1.8.1-next.0",
50
+ "@backstage/backend-plugin-api": "1.9.1-next.1",
51
+ "@backstage/catalog-model": "1.8.1-next.1",
52
52
  "@backstage/config": "1.3.8-next.0",
53
53
  "@backstage/errors": "1.3.1-next.0",
54
- "@backstage/plugin-auth-node": "0.7.1-next.0",
55
- "@backstage/plugin-catalog-node": "2.2.1-next.0",
54
+ "@backstage/plugin-auth-node": "0.7.1-next.1",
55
+ "@backstage/plugin-catalog-node": "2.2.1-next.1",
56
56
  "@backstage/types": "1.2.2",
57
57
  "@google-cloud/firestore": "^7.0.0",
58
58
  "connect-session-knex": "^4.0.0",
@@ -68,14 +68,13 @@
68
68
  "matcher": "^4.0.0",
69
69
  "minimatch": "^10.2.1",
70
70
  "passport": "^0.7.0",
71
- "uuid": "^11.0.0",
72
71
  "zod": "^3.25.76 || ^4.0.0",
73
72
  "zod-validation-error": "^5.0.0"
74
73
  },
75
74
  "devDependencies": {
76
- "@backstage/backend-defaults": "0.17.1-next.0",
77
- "@backstage/backend-test-utils": "1.11.3-next.0",
78
- "@backstage/cli": "0.36.2-next.0",
75
+ "@backstage/backend-defaults": "0.17.1-next.2",
76
+ "@backstage/backend-test-utils": "1.11.3-next.2",
77
+ "@backstage/cli": "0.36.2-next.1",
79
78
  "@backstage/plugin-auth-backend-module-google-provider": "0.3.15-next.0",
80
79
  "@backstage/plugin-auth-backend-module-guest-provider": "0.2.19-next.0",
81
80
  "@types/cookie-parser": "^1.4.2",