diff --git a/EXAMPLES.md b/EXAMPLES.md index b764fb57..614e2436 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -667,7 +667,7 @@ function MyComponent() { ## Android Networking Configuration -> **Platform Support:** Android only. Accepted on iOS for API compatibility but has no effect. +> **Platform Support:** The timeout and header fields are Android only and have no effect on iOS. `enableLogging` is honored on both iOS and Android. The `networkingOptions` configuration option lets you tune the native networking client (`DefaultClient` from Auth0.Android's OkHttp-based stack) used for every request the native SDK makes on your behalf — web auth token exchange, credential renewal, MFA, passkeys, and My Account API calls. @@ -685,7 +685,8 @@ networkingOptions?: { Any option you omit falls back to Auth0.Android's own default. > [!WARNING] -> `enableLogging` is **debug-only**. When enabled, Auth0.Android logs full HTTP request and response bodies to Logcat — including access, refresh, and ID tokens returned from token-endpoint calls, in plaintext. Never enable it in a production build. +> On **Android**, `enableLogging` is **debug-only**: Auth0.Android logs full HTTP request and response bodies to Logcat — including access, refresh, and ID tokens from token-endpoint calls, in plaintext — and the flag is ignored on release builds. Never enable it in a production build. +> On **iOS**, `enableLogging` maps to Auth0.swift's `.logging(enabled:)`, which traces requests and responses to the unified logging system (OSLog) with access, refresh, and ID tokens redacted. There is no release-build gate, so enable it only while debugging. ### Using Networking Options with Hooks diff --git a/ios/A0Auth0.mm b/ios/A0Auth0.mm index 11da8955..5cc4b160 100644 --- a/ios/A0Auth0.mm +++ b/ios/A0Auth0.mm @@ -109,8 +109,10 @@ - (dispatch_queue_t)methodQueue networkingOptions:(NSDictionary * _Nullable)networkingOptions resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - // networkingOptions is Android-only; intentionally not forwarded to NativeBridge. - [self tryAndInitializeNativeBridge:clientId domain:domain withLocalAuthenticationOptions:localAuthenticationOptions useDPoP:useDPoP maxRetries:(NSInteger)maxRetries credentialsManagerStorageKey:credentialsManagerStorageKey resolve:resolve reject:reject]; + // networkingOptions is otherwise Android-only; on iOS we honor just `enableLogging`, + // which maps to Auth0.swift's `.logging(enabled:)` (HTTP tracing with token redaction). + BOOL enableLogging = [networkingOptions[@"enableLogging"] boolValue]; + [self tryAndInitializeNativeBridge:clientId domain:domain withLocalAuthenticationOptions:localAuthenticationOptions useDPoP:useDPoP maxRetries:(NSInteger)maxRetries credentialsManagerStorageKey:credentialsManagerStorageKey enableLogging:enableLogging resolve:resolve reject:reject]; } @@ -415,12 +417,12 @@ - (BOOL)checkHasValidNativeBridgeInstance:(NSString*) clientId domain:(NSString return valid; } -- (void)tryAndInitializeNativeBridge:(NSString *)clientId domain:(NSString *)domain withLocalAuthenticationOptions:(NSDictionary*) options useDPoP:(NSNumber *)useDPoP maxRetries:(NSInteger)maxRetries credentialsManagerStorageKey:(NSString * _Nullable)credentialsManagerStorageKey resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { +- (void)tryAndInitializeNativeBridge:(NSString *)clientId domain:(NSString *)domain withLocalAuthenticationOptions:(NSDictionary*) options useDPoP:(NSNumber *)useDPoP maxRetries:(NSInteger)maxRetries credentialsManagerStorageKey:(NSString * _Nullable)credentialsManagerStorageKey enableLogging:(BOOL)enableLogging resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { BOOL useDPoPBool = [useDPoP boolValue]; - NativeBridge *bridge = [[NativeBridge alloc] initWithClientId:clientId domain:domain localAuthenticationOptions:options useDPoP:useDPoPBool maxRetries:maxRetries credentialsManagerStorageKey:credentialsManagerStorageKey resolve:resolve reject:reject]; + NativeBridge *bridge = [[NativeBridge alloc] initWithClientId:clientId domain:domain localAuthenticationOptions:options useDPoP:useDPoPBool maxRetries:maxRetries credentialsManagerStorageKey:credentialsManagerStorageKey enableLogging:enableLogging resolve:resolve reject:reject]; self.nativeBridge = bridge; - self.myAccount = [[A0MyAccount alloc] initWithDomain:domain useDPoP:useDPoPBool]; - self.passwordless = [[A0Passwordless alloc] initWithClientId:clientId domain:domain useDPoP:useDPoPBool]; + self.myAccount = [[A0MyAccount alloc] initWithDomain:domain useDPoP:useDPoPBool enableLogging:enableLogging]; + self.passwordless = [[A0Passwordless alloc] initWithClientId:clientId domain:domain useDPoP:useDPoPBool enableLogging:enableLogging]; } - (std::shared_ptr)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params { return std::make_shared(params); diff --git a/ios/A0MfaClient.swift b/ios/A0MfaClient.swift index 904842c1..6ef08a05 100644 --- a/ios/A0MfaClient.swift +++ b/ios/A0MfaClient.swift @@ -31,19 +31,22 @@ class A0MfaClient { private let clientId: String private let domain: String private let useDPoP: Bool + private let enableLogging: Bool private lazy var mfaClient: MFAClient = { var client = Auth0.mfa(clientId: clientId, domain: domain) if useDPoP { client = client.useDPoP() } + client = client.logging(enabled: enableLogging) return client }() - init(clientId: String, domain: String, useDPoP: Bool) { + init(clientId: String, domain: String, useDPoP: Bool, enableLogging: Bool) { self.clientId = clientId self.domain = domain self.useDPoP = useDPoP + self.enableLogging = enableLogging } // Maps the public MfaFactorType vocabulary (otp/sms/voice/email/push) onto diff --git a/ios/MyAccount.swift b/ios/MyAccount.swift index c1d742f4..2fe8b8f0 100644 --- a/ios/MyAccount.swift +++ b/ios/MyAccount.swift @@ -8,10 +8,12 @@ public class A0MyAccount: NSObject { private let domain: String private let useDPoP: Bool + private let enableLogging: Bool - @objc public init(domain: String, useDPoP: Bool) { + @objc public init(domain: String, useDPoP: Bool, enableLogging: Bool) { self.domain = domain self.useDPoP = useDPoP + self.enableLogging = enableLogging } private func createClient(accessToken: String) -> any MyAccount { @@ -19,6 +21,7 @@ public class A0MyAccount: NSObject { if self.useDPoP { client = client.useDPoP() } + client = client.logging(enabled: self.enableLogging) return client } diff --git a/ios/NativeBridge.swift b/ios/NativeBridge.swift index 79ed6ba9..98b9ad3b 100644 --- a/ios/NativeBridge.swift +++ b/ios/NativeBridge.swift @@ -48,20 +48,23 @@ public class NativeBridge: NSObject { var domain: String var useDPoP: Bool var maxRetries: Int + var enableLogging: Bool private(set) lazy var mfaClient: A0MfaClient = { - A0MfaClient(clientId: self.clientId, domain: self.domain, useDPoP: self.useDPoP) + A0MfaClient(clientId: self.clientId, domain: self.domain, useDPoP: self.useDPoP, enableLogging: self.enableLogging) }() - - @objc public init(clientId: String, domain: String, localAuthenticationOptions: [String: Any]?, useDPoP: Bool, maxRetries: Int, credentialsManagerStorageKey: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { + + @objc public init(clientId: String, domain: String, localAuthenticationOptions: [String: Any]?, useDPoP: Bool, maxRetries: Int, credentialsManagerStorageKey: String?, enableLogging: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { var auth0 = Auth0 .authentication(clientId: clientId, domain: domain) self.clientId = clientId self.domain = domain self.useDPoP = useDPoP self.maxRetries = maxRetries + self.enableLogging = enableLogging if self.useDPoP { auth0 = auth0.useDPoP() } + auth0 = auth0.logging(enabled: enableLogging) // Namespace the keychain per client when a storage key is provided, else use the default service. if let key = credentialsManagerStorageKey, !key.isEmpty { self.credentialsManager = CredentialsManager(authentication: auth0, storage: SimpleKeychain(service: key), maxRetries: maxRetries) @@ -104,6 +107,7 @@ public class NativeBridge: NSObject { if self.useDPoP { builder = builder.useDPoP() } + builder = builder.logging(enabled: self.enableLogging) if let value = URL(string: redirectUri) { builder = builder.redirectURL(value) } @@ -160,6 +164,7 @@ public class NativeBridge: NSObject { @objc public func webAuthLogout(scheme: String, federated: Bool, redirectUri: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) { var builder = Auth0.webAuth(clientId: self.clientId, domain: self.domain) + builder = builder.logging(enabled: self.enableLogging) if let value = URL(string: redirectUri) { builder = builder.redirectURL(value) } @@ -423,6 +428,7 @@ public class NativeBridge: NSObject { if self.useDPoP { auth = auth.useDPoP() } + auth = auth.logging(enabled: self.enableLogging) let finalScope = scope ?? "openid profile email" @@ -491,6 +497,7 @@ public class NativeBridge: NSObject { if self.useDPoP { auth = auth.useDPoP() } + auth = auth.logging(enabled: self.enableLogging) auth.passkeySignupChallenge( email: finalEmail, @@ -539,6 +546,7 @@ public class NativeBridge: NSObject { if self.useDPoP { auth = auth.useDPoP() } + auth = auth.logging(enabled: self.enableLogging) auth.passkeyLoginChallenge( connection: realmValue, @@ -590,6 +598,7 @@ public class NativeBridge: NSObject { if self.useDPoP { auth = auth.useDPoP() } + auth = auth.logging(enabled: self.enableLogging) if let attestationObjectString = responseDict["attestationObject"] as? String { let attestationObject = Data(base64URLEncoded: attestationObjectString) diff --git a/ios/Passwordless.swift b/ios/Passwordless.swift index 2f52bc87..60cf7f79 100644 --- a/ios/Passwordless.swift +++ b/ios/Passwordless.swift @@ -7,11 +7,12 @@ public class A0Passwordless: NSObject { private let client: Authentication - @objc public init(clientId: String, domain: String, useDPoP: Bool) { + @objc public init(clientId: String, domain: String, useDPoP: Bool, enableLogging: Bool) { var client = Auth0.authentication(clientId: clientId, domain: domain) if useDPoP { client = client.useDPoP() } + client = client.logging(enabled: enableLogging) self.client = client } diff --git a/src/platforms/native/bridge/NativeBridge.ts b/src/platforms/native/bridge/NativeBridge.ts index 0eb176dd..a69ee2d6 100644 --- a/src/platforms/native/bridge/NativeBridge.ts +++ b/src/platforms/native/bridge/NativeBridge.ts @@ -40,7 +40,7 @@ export interface NativeBridge { * @param useDPoP Whether to enable DPoP (Demonstrating Proof-of-Possession) for token requests. * @param maxRetries The maximum number of retry attempts for transient errors during credential renewal. **iOS only** - ignored on Android. Defaults to 0. * @param credentialsManagerStorageKey Namespaces the credentials store. **Android only** SharedPreferences file name. **iOS only** Keychain service name. Defaults to the shared store when omitted. - * @param networkingOptions Configures the native networking client. **Android only** - ignored on iOS. + * @param networkingOptions Configures the native networking client. Timeouts and headers are **Android only**; `enableLogging` is honored on both platforms. */ initialize( clientId: string, diff --git a/src/types/common.ts b/src/types/common.ts index 7b1fa97f..28678b4d 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -217,7 +217,8 @@ export interface Auth0Options { /** * Configures the native networking client (OkHttp) that Auth0.Android uses for every * request it makes (web auth token exchange, credential renewal, MFA, passkeys, etc.). - * @remarks Android only. Accepted on iOS for API compatibility but has no effect. + * @remarks The timeout and header fields are Android only and have no effect on iOS. + * `enableLogging` is honored on both platforms (see {@link NetworkingOptions.enableLogging}). */ networkingOptions?: NetworkingOptions; // Telemetry and localAuthenticationOptions are platform-specific extensions @@ -227,7 +228,8 @@ export interface Auth0Options { * Configuration for the native networking client used by Auth0.Android. * Mirrors `DefaultClient.Builder` from the Auth0.Android SDK. * - * @remarks Android only. Has no effect on iOS or web. + * @remarks The timeout and header fields are Android only and have no effect on iOS or web. + * `enableLogging` is honored on iOS as well (it maps to Auth0.swift's `.logging(enabled:)`). * * @example * ```ts @@ -254,12 +256,16 @@ export interface NetworkingOptions { */ defaultHeaders?: Record; /** - * Enables verbose HTTP request/response logging to Logcat. + * Enables verbose HTTP request/response logging for the native SDKs. * * @remarks - * **Debug-only.** Auth0.Android logs full request and response bodies at this level, - * which includes access, refresh, and ID tokens in plaintext for token-endpoint calls. - * Never enable this in production. + * On **Android** this maps to the OkHttp logging interceptor and writes full request and + * response bodies to Logcat — including access, refresh, and ID tokens in plaintext — so it is + * **debug-only** (ignored on release builds) and must never be enabled in production. + * + * On **iOS** this maps to Auth0.swift's `.logging(enabled:)`, which traces requests and responses + * to the unified logging system (OSLog) with access, refresh, and ID tokens redacted. It has no + * build-type gate, so still enable it only while debugging. * @default false */ enableLogging?: boolean;