diff --git a/index.js b/index.js index 56062317..568d7066 100644 --- a/index.js +++ b/index.js @@ -61,6 +61,8 @@ export const createConfig = async({ endSessionRedirectUri, discoveryUri, scopes, + keychainService = 'Okta', + keychainAccessGroup = 'com.okta.mobile-sdk.shared', requireHardwareBackedKeyStore, androidChromeTabColor, httpConnectionTimeout, @@ -110,6 +112,8 @@ export const createConfig = async({ endSessionRedirectUri, discoveryUri, scopes, + keychainService, + keychainAccessGroup, userAgentTemplate, httpConnectionTimeout, ); diff --git a/ios/OktaSdkBridge/OktaSdkBridge.swift b/ios/OktaSdkBridge/OktaSdkBridge.swift index 2169b1da..bb2758d3 100644 --- a/ios/OktaSdkBridge/OktaSdkBridge.swift +++ b/ios/OktaSdkBridge/OktaSdkBridge.swift @@ -52,6 +52,38 @@ extension OktaOidcStateManager: StateManagerProtocol { } +// MARK: - DeviceSecretKeychain + +class DeviceSecretKeychain { + var service: String + var accessGroup: String + + init(service: String, accessGroup: String) { + self.service = service + self.accessGroup = accessGroup + } + + func save(_ tokens: [String: String]) { + let query: [String: Any] = [ + (kSecClass as String): kSecClassGenericPassword, + (kSecAttrService as String): service, + (kSecAttrAccessGroup as String): self.accessGroup] + SecItemDelete(query as CFDictionary) + + for (kind, value) in tokens { + let attributes: [String: Any] = [ + (kSecClass as String): kSecClassGenericPassword, + (kSecAttrService as String): service, + (kSecAttrAccessGroup as String): self.accessGroup, + (kSecAttrAccount as String): "Okta-\(kind)", + (kSecValueData as String): value.data(using: .utf8)! + ] + + SecItemAdd(attributes as CFDictionary, nil) + } + } +} + // MARK: - OktaSdkBridge @objc(OktaSdkBridge) @@ -70,10 +102,11 @@ class OktaSdkBridge: RCTEventEmitter { } var oktaOidc: OktaOidcProtocol? - + override var methodQueue: DispatchQueue { .main } private var requestTimeout: Int? + private var deviceSecretKeychain: DeviceSecretKeychain? func presentedViewController() -> UIViewController? { RCTPresentedViewController() @@ -85,6 +118,8 @@ class OktaSdkBridge: RCTEventEmitter { endSessionRedirectUri: String, discoveryUri: String, scopes: String, + keychainService: String, + keychainAccessGroup: String, userAgentTemplate: String, requestTimeout: Int, promiseResolver: RCTPromiseResolveBlock, @@ -105,6 +140,7 @@ class OktaSdkBridge: RCTEventEmitter { oktaOidc = try OktaOidc(configuration: config) self.requestTimeout = requestTimeout + self.deviceSecretKeychain = DeviceSecretKeychain(service: keychainService, accessGroup: keychainAccessGroup) promiseResolver(true) } catch let error { @@ -311,6 +347,15 @@ class OktaSdkBridge: RCTEventEmitter { } currStateManager.writeToSecureStorage() + + // Only use the DeviceSecretKeychain if the response includes a "device_secret" field + if let deviceSecret = currStateManager.authState.lastTokenResponse!.additionalParameters!["device_secret"] as? String { + self.deviceSecretKeychain!.save([ + "id_token": currStateManager.idToken!, + "device_secret": deviceSecret + ]) + } + let dic = [ OktaSdkConstant.RESOLVE_TYPE_KEY: OktaSdkConstant.AUTHORIZED, OktaSdkConstant.ACCESS_TOKEN_KEY: stateManager?.accessToken diff --git a/ios/OktaSdkBridge/ReactNativeOktaSdkBridge.m b/ios/OktaSdkBridge/ReactNativeOktaSdkBridge.m index 67538f0a..0418c197 100644 --- a/ios/OktaSdkBridge/ReactNativeOktaSdkBridge.m +++ b/ios/OktaSdkBridge/ReactNativeOktaSdkBridge.m @@ -21,6 +21,8 @@ @interface RCT_EXTERN_MODULE(OktaSdkBridge, RCTEventEmitter) endSessionRedirectUri:(NSString *)endSessionRedirectUri discoveryUri:(NSString *)discoveryUri scopes:(NSString *)scopes + keychainService:(NSString *)keychainService + keychainAccessGroup:(NSString *)keychainAccessGroup userAgentTemplate:(NSString *)userAgentTemplate requestTimeout:(NSInteger)requestTimeout promiseResolver:(RCTPromiseResolveBlock *)promiseResolver diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9b843b5d..f7f076df 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -11,11 +11,11 @@ PODS: - ReactCommon/turbomodule/core (= 0.68.1) - fmt (6.2.1) - glog (0.3.5) - - OktaOidc (3.11.0): - - OktaOidc/AppAuth (= 3.11.0) - - OktaOidc/Okta (= 3.11.0) - - OktaOidc/AppAuth (3.11.0) - - OktaOidc/Okta (3.11.0): + - OktaOidc (3.11.1): + - OktaOidc/AppAuth (= 3.11.1) + - OktaOidc/Okta (= 3.11.1) + - OktaOidc/AppAuth (3.11.1) + - OktaOidc/Okta (3.11.1): - OktaOidc/AppAuth - RCT-Folly (2021.06.28.00-v2): - boost @@ -402,9 +402,9 @@ SPEC CHECKSUMS: FBLazyVector: 2c76493a346ef8cacf1f442926a39f805fffec1f FBReactNativeSpec: 371350f24afa87b6aba606972ec959dcd4a95c9a fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 85ecdd10ee8d8ec362ef519a6a45ff9aa27b2e85 - OktaOidc: 7a6a9a827722b26fded69c9715e606f5abc3a3a7 - RCT-Folly: 803a9cfd78114b2ec0f140cfa6fa2a6bafb2d685 + glog: 476ee3e89abb49e07f822b48323c51c57124b572 + OktaOidc: 94d65a417fefbdd92441a6e8b8347e2db2ea885a + RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8 RCTRequired: 00581111c53531e39e3c6346ef0d2c0cf52a5a37 RCTTypeSafety: 07e03ee7800e7dd65cba8e52ad0c2edb06c96604 React: e61f4bf3c573d0c61c56b53dc3eb1d9daf0768a0 diff --git a/package.json b/package.json index 2bf0cd37..ce1f4d8d 100644 --- a/package.json +++ b/package.json @@ -106,4 +106,4 @@ "tsd": { "directory": "./dist/types" } -} \ No newline at end of file +} diff --git a/test/index.test.js b/test/index.test.js index ad26c60f..748fff2f 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -148,7 +148,14 @@ describe('OktaReactNative', () => { Platform.OS = 'ios'; Platform.Version = '1.0.0'; const processedScope = config.scopes.join(' '); + + config = { + ...config, + keychainService: 'Test', + keychainAccessGroup: 'com.example.SharedItems', + } createConfig(config); + expect(mockCreateConfig).toHaveBeenCalledTimes(1); expect(mockCreateConfig).toHaveBeenCalledWith( config.clientId, @@ -156,6 +163,8 @@ describe('OktaReactNative', () => { config.endSessionRedirectUri, config.discoveryUri, processedScope, + config.keychainService, + config.keychainAccessGroup, `okta-react-native/${version} $UPSTREAM_SDK react-native/${reactNativeVersion} ios/1.0.0`, defaultTimeouts.httpConnectionTimeout, ); diff --git a/types/index.d.ts b/types/index.d.ts index f3ad4ed3..af4f02cd 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -63,6 +63,8 @@ export namespace Okta { endSessionRedirectUri: string; discoveryUri: string; scopes: string[]; + keychainService?: string; + keychainAccessGroup?: string; requireHardwareBackedKeyStore: boolean; androidChromeTabColor?: string; httpConnectionTimeout?: number; diff --git a/types/index.test-d.ts b/types/index.test-d.ts index 96766cd3..9a217091 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -23,6 +23,8 @@ expectType>(OktaSDK.createConfig({ endSessionRedirectUri: '{endSessionRedirectUri}', discoveryUri: '{discoveryUri}', scopes: ['scope1', 'scope2'], + keychainService: 'Test'; + keychainAccessGroup: 'com.example.SharedItems'; requireHardwareBackedKeyStore: false, androidChromeTabColor: '#00000', httpConnectionTimeout: 15,