@@ -140,6 +140,31 @@ extension Auth: AuthInterop {
140
140
}
141
141
}
142
142
143
+ /// Holds configuration for a R-GCIP tenant.
144
+ public struct TenantConfig : Sendable {
145
+ public let tenantId : String /// The ID of the tenant.
146
+ public let location : String /// The location of the tenant.
147
+
148
+ /// Initializes a `TenantConfig` instance.
149
+ /// - Parameters:
150
+ /// - location: The location of the tenant, defaults to "prod-global".
151
+ /// - tenantId: The ID of the tenant.
152
+ public init ( tenantId: String , location: String = " prod-global " ) {
153
+ self . location = location
154
+ self . tenantId = tenantId
155
+ }
156
+ }
157
+
158
+ /// Holds a Firebase ID token and its expiration.
159
+ public struct FirebaseToken : Sendable {
160
+ public let token : String
161
+ public let expirationDate : Date
162
+ init ( token: String , expirationDate: Date ) {
163
+ self . token = token
164
+ self . expirationDate = expirationDate
165
+ }
166
+ }
167
+
143
168
/// Manages authentication for Firebase apps.
144
169
///
145
170
/// This class is thread-safe.
@@ -170,6 +195,20 @@ extension Auth: AuthInterop {
170
195
/// Gets the `FirebaseApp` object that this auth object is connected to.
171
196
@objc public internal( set) weak var app : FirebaseApp ?
172
197
198
+ /// Gets the auth object for a `FirebaseApp` with an optional `TenantConfig`.
199
+ /// - Parameters:
200
+ /// - app: The Firebase app instance.
201
+ /// - tenantConfig: The optional configuration for the RGCIP.
202
+ /// - Returns: The `Auth` instance associated with the given app and tenant config.
203
+ public static func auth( app: FirebaseApp , tenantConfig: TenantConfig ) -> Auth {
204
+ let auth = auth ( app: app)
205
+ kAuthGlobalWorkQueue. sync {
206
+ auth. requestConfiguration. location = tenantConfig. location
207
+ auth. requestConfiguration. tenantId = tenantConfig. tenantId
208
+ }
209
+ return auth
210
+ }
211
+
173
212
/// Synchronously gets the cached current user, or null if there is none.
174
213
@objc public var currentUser : User ? {
175
214
kAuthGlobalWorkQueue. sync {
@@ -2425,3 +2464,89 @@ extension Auth: AuthInterop {
2425
2464
/// Mutations should occur within a @synchronized(self) context.
2426
2465
private var listenerHandles : NSMutableArray = [ ]
2427
2466
}
2467
+
2468
+ @available ( iOS 13 , * )
2469
+ public extension Auth {
2470
+ /// Exchanges a third-party OIDC token for a Firebase STS token.
2471
+ ///
2472
+ /// This method is used in R-GCIP (multi-tenant) environments where the `Auth` instance must
2473
+ /// be configured with a `TenantConfig`, including `location` and `tenantId`.
2474
+ /// Unlike other sign-in methods, this flow *does not* create or update a `User` object.
2475
+ ///
2476
+ /// - Parameters:
2477
+ /// - request: The ExchangeTokenRequest containing the OIDC token and other parameters.
2478
+ /// - completion: A closure that gets called with either an `AuthTokenResult` or an `Error`.
2479
+ func exchangeToken( customToken: String ,
2480
+ idpConfigId: String ,
2481
+ useStaging: Bool = false ,
2482
+ completion: @escaping ( FirebaseToken ? , Error ? ) -> Void ) {
2483
+ // Ensure R-GCIP is configured with location and tenant ID
2484
+ guard let _ = requestConfiguration. location,
2485
+ let _ = requestConfiguration. tenantId
2486
+ else {
2487
+ Auth . wrapMainAsync (
2488
+ callback: completion,
2489
+ with: . failure( AuthErrorUtils
2490
+ . operationNotAllowedError ( message: " R-GCIP is not configured. " ) )
2491
+ )
2492
+ return
2493
+ }
2494
+ let request = ExchangeTokenRequest (
2495
+ customToken: customToken,
2496
+ idpConfigID: idpConfigId,
2497
+ config: requestConfiguration,
2498
+ useStaging: true
2499
+ )
2500
+ Task {
2501
+ do {
2502
+ let response = try await backend. call ( with: request)
2503
+ let firebaseToken = FirebaseToken (
2504
+ token: response. firebaseToken,
2505
+ expirationDate: response. expirationDate
2506
+ )
2507
+ Auth . wrapMainAsync ( callback: completion, with: . success( firebaseToken) )
2508
+ } catch {
2509
+ Auth . wrapMainAsync ( callback: completion, with: . failure( error) )
2510
+ }
2511
+ }
2512
+ }
2513
+
2514
+ /// Exchanges a third-party OIDC token for a Firebase STS token using Swift concurrency.
2515
+ ///
2516
+ /// This async variant performs the same operation as the completion-based method but returns
2517
+ /// the result directly and throws on failure.
2518
+ ///
2519
+ /// The `Auth` instance must be configured with `TenantConfig` containing `location` and
2520
+ /// `tenantId`.
2521
+ /// Unlike other sign-in methods, this flow *does not* create or update a `User` object.
2522
+ ///
2523
+ /// - Parameters:
2524
+ /// - request: The ExchangeTokenRequest containing the OIDC token and other parameters.
2525
+ /// - Returns: An `AuthTokenResult` containing the Firebase ID token and its expiration details.
2526
+ /// - Throws: An error if R-GCIP is not configured, if the network call fails,
2527
+ /// or if the token parsing fails.
2528
+ func exchangeToken( customToken: String , idpConfigId: String ,
2529
+ useStaging: Bool = false ) async throws -> FirebaseToken {
2530
+ // Ensure R-GCIP is configured with location and tenant ID
2531
+ guard let _ = requestConfiguration. location,
2532
+ let _ = requestConfiguration. tenantId
2533
+ else {
2534
+ throw AuthErrorUtils . operationNotAllowedError ( message: " R-GCIP is not configured. " )
2535
+ }
2536
+ let request = ExchangeTokenRequest (
2537
+ customToken: customToken,
2538
+ idpConfigID: idpConfigId,
2539
+ config: requestConfiguration,
2540
+ useStaging: true
2541
+ )
2542
+ do {
2543
+ let response = try await backend. call ( with: request)
2544
+ return FirebaseToken (
2545
+ token: response. firebaseToken,
2546
+ expirationDate: response. expirationDate
2547
+ )
2548
+ } catch {
2549
+ throw error
2550
+ }
2551
+ }
2552
+ }
0 commit comments