Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ class AppManager {
private var otherApp: FirebaseApp
var app: FirebaseApp

// Initialise Auth with TenantConfig
let tenantConfig = Auth.TenantConfig(tenantId: "{{TENANT_ID}}", location: "{{LOCATION}}")
func auth() -> Auth {
return Auth.auth(app: app)
return Auth.auth(app: app, tenantConfig: tenantConfig)
}

private init() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ enum AuthMenu: String {
case phoneEnroll
case totpEnroll
case multifactorUnenroll
case exchangeToken

// More intuitively named getter for `rawValue`.
var id: String { rawValue }
Expand Down Expand Up @@ -139,6 +140,9 @@ enum AuthMenu: String {
return "TOTP Enroll"
case .multifactorUnenroll:
return "Multifactor unenroll"
// R-GCIP Exchange Token
case .exchangeToken:
return "Exchange Token"
}
}

Expand Down Expand Up @@ -220,6 +224,8 @@ enum AuthMenu: String {
self = .totpEnroll
case "Multifactor unenroll":
self = .multifactorUnenroll
case "Exchange Token":
self = .exchangeToken
default:
return nil
}
Expand Down Expand Up @@ -354,9 +360,17 @@ class AuthMenuData: DataSourceProvidable {
return Section(headerDescription: header, items: items)
}

static var exchangeTokenSection: Section {
let header = "Exchange Token [Regionalized Auth]"
let items: [Item] = [
Item(title: AuthMenu.exchangeToken.name),
]
return Section(headerDescription: header, items: items)
}

static let sections: [Section] =
[settingsSection, providerSection, emailPasswordSection, otherSection, recaptchaSection,
customAuthDomainSection, appSection, oobSection, multifactorSection]
customAuthDomainSection, appSection, oobSection, multifactorSection, exchangeTokenSection]

static var authLinkSections: [Section] {
let allItems = [providerSection, emailPasswordSection, otherSection].flatMap { $0.items }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate {

case .multifactorUnenroll:
mfaUnenroll()

case .exchangeToken:
callExchangeToken()
}
}

Expand Down Expand Up @@ -1085,4 +1088,110 @@ extension AuthViewController: ASAuthorizationControllerDelegate,
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
return view.window!
}

/// Orchestrates the UI flow to demonstrate the OIDC token exchange feature.
///
/// This function sequentially prompts the user for the necessary inputs (idpConfigID and custom
/// token) using async/await with UIAlerts. If both inputs are provided,
/// it calls the Auth.exchangeToken API and displays the result to the user.
private func callExchangeToken() {
Task {
do {
// 1. Prompt for the IDP Config ID and await user input.
guard let idpConfigId = await showTextInputPrompt(with: "Enter IDP Config ID:") else {
print("Token exchange cancelled: IDP Config ID was not provided.")
// Present an alert on the main thread to indicate cancellation.
DispatchQueue.main.async {
let alert = UIAlertController(title: "Cancelled",
message: "An IDP Config ID is required to proceed.",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}
return
}

// 2. Prompt for the custom OIDC token and await user input.
guard let idToken = await showTextInputPrompt(with: "Enter OIDC Token:") else {
print("Token exchange cancelled: OIDC Token was not provided.")
// Present an alert on the main thread to indicate cancellation.
DispatchQueue.main.async {
let alert = UIAlertController(title: "Cancelled",
message: "An OIDC Token is required to proceed.",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}
return
}

// 3. With both inputs, call the exchangeToken API.
// The `auth()` instance is pre-configured with a regional tenant in AppManager.
print("Attempting to exchange token...")
let result = try await AppManager.shared.auth().exchangeToken(
idToken: idToken,
idpConfigId: idpConfigId,
useStaging: true
)

// 4. Handle the success case by presenting an alert on the main thread.
print("Token exchange successful. Access Token: \(result.token)")
DispatchQueue.main.async {
let fullToken = result.token
let truncatedToken = self.truncateString(fullToken, maxLength: 20)
let message = "Firebase Access Token:\n\(truncatedToken)"
let alert = UIAlertController(
title: "Token Exchange Succeeded",
message: message,
preferredStyle: .alert
)
// Action to copy the token
let copyAction = UIAlertAction(title: "Copy Token", style: .default) { _ in
UIPasteboard.general.string = fullToken
// Show a brief confirmation
self.showCopyConfirmation()
}
alert.addAction(copyAction)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}

} catch {
// 5. Handle any errors during the process by presenting an alert on the main thread.
print("Failed to exchange token: \(error)")
DispatchQueue.main.async {
let alert = UIAlertController(
title: "Token Exchange Error",
message: error.localizedDescription,
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "OK", style: .default))
self.present(alert, animated: true)
}
}
}
}

// Helper function to truncate strings
private func truncateString(_ string: String, maxLength: Int) -> String {
if string.count > maxLength {
return String(string.prefix(maxLength)) + "..."
} else {
return string
}
}

// Helper function to show copy confirmation
private func showCopyConfirmation() {
let confirmationAlert = UIAlertController(
title: "Copied!",
message: "Token copied to clipboard.",
preferredStyle: .alert
)
present(confirmationAlert, animated: true)
// Automatically dismiss the confirmation after a short delay
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
confirmationAlert.dismiss(animated: true, completion: nil)
}
}
}
Loading