-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Enable OIDC Token Exchange for BYO-CIAM (R-GCIP) #14983
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: exchange-token-implementation
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -151,8 +151,22 @@ public struct TenantConfig: Sendable { | |
/// - tenantId: The ID of the tenant. | ||
/// - location: The location of the tenant. Defaults to "prod-global". | ||
public init(tenantId: String, location: String = "prod-global") { | ||
self.location = location | ||
self.tenantId = tenantId | ||
self.location = location | ||
} | ||
} | ||
|
||
/// Represents the result of a successful OIDC token exchange, containing a Firebase ID token | ||
/// and its expiration. | ||
public struct FirebaseToken: Sendable { | ||
/// The Firebase ID token string. | ||
public let token: String | ||
/// The date at which the Firebase ID token expires. | ||
public let expirationDate: Date | ||
|
||
init(token: String, expirationDate: Date) { | ||
self.token = token | ||
self.expirationDate = expirationDate | ||
} | ||
} | ||
|
||
|
@@ -186,16 +200,10 @@ public struct TenantConfig: Sendable { | |
/// Gets the `FirebaseApp` object that this auth object is connected to. | ||
@objc public internal(set) weak var app: FirebaseApp? | ||
|
||
/// Gets the Auth object for a `FirebaseApp` configured for a specific Regional Google Cloud | ||
/// Identity Platform (R-GCIP) tenant. | ||
/// | ||
/// Use this method to create an `Auth` instance that interacts with a regionalized | ||
/// authentication backend instead of the default endpoint. | ||
/// | ||
/// Gets the auth object for a `FirebaseApp` with an optional `TenantConfig`. | ||
/// - Parameters: | ||
/// - app: The Firebase app instance. | ||
/// - tenantConfig: The configuration for the R-GCIP tenant, specifying the tenant ID and its | ||
/// location. | ||
/// - tenantConfig: The optional configuration for the RGCIP. | ||
/// - Returns: The `Auth` instance associated with the given app and tenant config. | ||
public static func auth(app: FirebaseApp, tenantConfig: TenantConfig) -> Auth { | ||
let auth = auth(app: app) | ||
|
@@ -2461,3 +2469,103 @@ public struct TenantConfig: Sendable { | |
/// Mutations should occur within a @synchronized(self) context. | ||
private var listenerHandles: NSMutableArray = [] | ||
} | ||
|
||
@available(iOS 13, *) | ||
public extension Auth { | ||
/// Exchanges a third-party OIDC token for a Firebase ID token. | ||
/// | ||
/// This method is used for Bring Your Own CIAM (BYO-CIAM) in Regionalized GCIP (R-GCIP), | ||
/// where the `Auth` instance must be configured with a `TenantConfig`, including `location` | ||
/// and `tenantId`, typically by using `Auth.auth(app:tenantConfig:)`. | ||
/// | ||
/// Unlike standard sign-in methods, this flow *does not* create or update a `User` object and | ||
/// *does not* set `CurrentUser` on the `Auth` instance. It only returns a Firebase token. | ||
/// | ||
/// - Parameters: | ||
/// - oidcToken: The OIDC ID token obtained from the third-party identity provider. | ||
/// - idpConfigId: The ID of the Identity Provider configuration within your GCIP tenant | ||
/// (e.g., "oidc.my-provider"). | ||
/// - useStaging: A Boolean value indicating whether to use the staging Identity Platform | ||
/// backend. Defaults to `false`. | ||
/// - completion: A closure that is called asynchronously on the main thread with either a | ||
/// `FirebaseToken` on success or an `Error` on failure. | ||
func exchangeToken(idToken: String, | ||
idpConfigId: String, | ||
useStaging: Bool = false, | ||
completion: @escaping (FirebaseToken?, Error?) -> Void) { | ||
Comment on lines
+2492
to
+2495
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer if we don't introduce new API that uses completion handlers. As least initially, new async API should be async/await only unless there a reason for completion handler (e.g. objc support, etc.) |
||
/// Ensure R-GCIP is configured with location and tenant ID | ||
guard let _ = requestConfiguration.location, | ||
let _ = requestConfiguration.tenantId | ||
else { | ||
Auth.wrapMainAsync( | ||
callback: completion, | ||
with: .failure(AuthErrorUtils | ||
.operationNotAllowedError( | ||
message: "Auth instance must be configured with a TenantConfig, including location and tenantId, to use exchangeToken." | ||
)) | ||
) | ||
return | ||
} | ||
let request = ExchangeTokenRequest( | ||
idToken: idToken, | ||
idpConfigID: idpConfigId, | ||
config: requestConfiguration, | ||
useStaging: true | ||
) | ||
Task { | ||
do { | ||
let response = try await backend.call(with: request) | ||
let firebaseToken = FirebaseToken( | ||
token: response.firebaseToken, | ||
expirationDate: response.expirationDate | ||
) | ||
Auth.wrapMainAsync(callback: completion, with: .success(firebaseToken)) | ||
} catch { | ||
Auth.wrapMainAsync(callback: completion, with: .failure(error)) | ||
} | ||
} | ||
} | ||
|
||
/// Exchanges a third-party OIDC ID token for a Firebase ID token using Swift concurrency. | ||
/// | ||
/// This method is used for Bring Your Own CIAM (BYO-CIAM) in Regionalized GCIP (R-GCIP), | ||
/// where the `Auth` instance must be configured with a `TenantConfig`, including `location` | ||
/// and `tenantId`, typically by using `Auth.auth(app:tenantConfig:)`. | ||
/// | ||
/// Unlike standard sign-in methods, this flow *does not* create or update a `User`object and | ||
/// *does not* set `CurrentUser` on the `Auth` instance. It only returns a Firebase token. | ||
/// | ||
/// - Parameters: | ||
/// - oidcToken: The OIDC ID token obtained from the third-party identity provider. | ||
/// - idpConfigId: The ID of the Identity Provider configuration within your GCIP tenant | ||
/// (e.g., "oidc.my-provider"). | ||
/// - useStaging: A Boolean value indicating whether to use the staging Identity Platform | ||
/// backend. Defaults to `false`. | ||
/// - Returns: A `FirebaseToken` containing the Firebase ID token and its expiration date. | ||
/// - Throws: An error if the `Auth` instance is not configured for R-GCIP, if the network | ||
/// call fails, or if the token response parsing fails. | ||
func exchangeToken(idToken: String, idpConfigId: String, | ||
useStaging: Bool = false) async throws -> FirebaseToken { | ||
// Ensure R-GCIP is configured with location and tenant ID | ||
guard let _ = requestConfiguration.location, | ||
let _ = requestConfiguration.tenantId | ||
else { | ||
throw AuthErrorUtils.operationNotAllowedError(message: "R-GCIP is not configured.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think this should instead cause a fatal error? When do we anticipate this case triggering, during development or possibly in live app's execution? |
||
} | ||
let request = ExchangeTokenRequest( | ||
idToken: idToken, | ||
idpConfigID: idpConfigId, | ||
config: requestConfiguration, | ||
useStaging: true | ||
) | ||
do { | ||
let response = try await backend.call(with: request) | ||
return FirebaseToken( | ||
token: response.firebaseToken, | ||
expirationDate: response.expirationDate | ||
) | ||
} catch { | ||
throw error | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same point in the TenantConfig PR, is the nesting (e.g.
Auth.FirebaseToken
) intentional?