Skip to content

Commit e583fdd

Browse files
committed
feat: Store device secret in keychain
1 parent b1e23ac commit e583fdd

8 files changed

Lines changed: 74 additions & 10 deletions

File tree

index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ export const createConfig = async({
6161
endSessionRedirectUri,
6262
discoveryUri,
6363
scopes,
64+
keychainService = 'Okta',
65+
keychainAccessGroup = 'com.okta.mobile-sdk.shared',
6466
requireHardwareBackedKeyStore,
6567
androidChromeTabColor,
6668
httpConnectionTimeout,
@@ -110,6 +112,8 @@ export const createConfig = async({
110112
endSessionRedirectUri,
111113
discoveryUri,
112114
scopes,
115+
keychainService,
116+
keychainAccessGroup,
113117
userAgentTemplate,
114118
httpConnectionTimeout,
115119
);

ios/OktaSdkBridge/OktaSdkBridge.swift

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,38 @@ extension OktaOidcStateManager: StateManagerProtocol {
5252

5353
}
5454

55+
// MARK: - DeviceSecretKeychain
56+
57+
class DeviceSecretKeychain {
58+
var service: String
59+
var accessGroup: String
60+
61+
init(service: String, accessGroup: String) {
62+
self.service = service
63+
self.accessGroup = accessGroup
64+
}
65+
66+
func save(_ tokens: [String: String]) {
67+
let query: [String: Any] = [
68+
(kSecClass as String): kSecClassGenericPassword,
69+
(kSecAttrService as String): service,
70+
(kSecAttrAccessGroup as String): self.accessGroup]
71+
SecItemDelete(query as CFDictionary)
72+
73+
for (kind, value) in tokens {
74+
let attributes: [String: Any] = [
75+
(kSecClass as String): kSecClassGenericPassword,
76+
(kSecAttrService as String): service,
77+
(kSecAttrAccessGroup as String): self.accessGroup,
78+
(kSecAttrAccount as String): "Okta-\(kind)",
79+
(kSecValueData as String): value.data(using: .utf8)!
80+
]
81+
82+
SecItemAdd(attributes as CFDictionary, nil)
83+
}
84+
}
85+
}
86+
5587
// MARK: - OktaSdkBridge
5688

5789
@objc(OktaSdkBridge)
@@ -70,10 +102,11 @@ class OktaSdkBridge: RCTEventEmitter {
70102
}
71103

72104
var oktaOidc: OktaOidcProtocol?
73-
105+
74106
override var methodQueue: DispatchQueue { .main }
75107

76108
private var requestTimeout: Int?
109+
private var deviceSecretKeychain: DeviceSecretKeychain?
77110

78111
func presentedViewController() -> UIViewController? {
79112
RCTPresentedViewController()
@@ -85,6 +118,8 @@ class OktaSdkBridge: RCTEventEmitter {
85118
endSessionRedirectUri: String,
86119
discoveryUri: String,
87120
scopes: String,
121+
keychainService: String,
122+
keychainAccessGroup: String,
88123
userAgentTemplate: String,
89124
requestTimeout: Int,
90125
promiseResolver: RCTPromiseResolveBlock,
@@ -105,6 +140,7 @@ class OktaSdkBridge: RCTEventEmitter {
105140

106141
oktaOidc = try OktaOidc(configuration: config)
107142
self.requestTimeout = requestTimeout
143+
self.deviceSecretKeychain = DeviceSecretKeychain(service: keychainService, accessGroup: keychainAccessGroup)
108144

109145
promiseResolver(true)
110146
} catch let error {
@@ -311,6 +347,15 @@ class OktaSdkBridge: RCTEventEmitter {
311347
}
312348

313349
currStateManager.writeToSecureStorage()
350+
351+
// Only use the DeviceSecretKeychain if the response includes a "device_secret" field
352+
if let deviceSecret = currStateManager.authState.lastTokenResponse!.additionalParameters!["device_secret"] as? String {
353+
self.deviceSecretKeychain!.save([
354+
"id_token": currStateManager.idToken!,
355+
"device_secret": deviceSecret
356+
])
357+
}
358+
314359
let dic = [
315360
OktaSdkConstant.RESOLVE_TYPE_KEY: OktaSdkConstant.AUTHORIZED,
316361
OktaSdkConstant.ACCESS_TOKEN_KEY: stateManager?.accessToken

ios/OktaSdkBridge/ReactNativeOktaSdkBridge.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ @interface RCT_EXTERN_MODULE(OktaSdkBridge, RCTEventEmitter)
2121
endSessionRedirectUri:(NSString *)endSessionRedirectUri
2222
discoveryUri:(NSString *)discoveryUri
2323
scopes:(NSString *)scopes
24+
keychainService:(NSString *)keychainService
25+
keychainAccessGroup:(NSString *)keychainAccessGroup
2426
userAgentTemplate:(NSString *)userAgentTemplate
2527
requestTimeout:(NSInteger)requestTimeout
2628
promiseResolver:(RCTPromiseResolveBlock *)promiseResolver

ios/Podfile.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ PODS:
1111
- ReactCommon/turbomodule/core (= 0.68.1)
1212
- fmt (6.2.1)
1313
- glog (0.3.5)
14-
- OktaOidc (3.11.0):
15-
- OktaOidc/AppAuth (= 3.11.0)
16-
- OktaOidc/Okta (= 3.11.0)
17-
- OktaOidc/AppAuth (3.11.0)
18-
- OktaOidc/Okta (3.11.0):
14+
- OktaOidc (3.11.1):
15+
- OktaOidc/AppAuth (= 3.11.1)
16+
- OktaOidc/Okta (= 3.11.1)
17+
- OktaOidc/AppAuth (3.11.1)
18+
- OktaOidc/Okta (3.11.1):
1919
- OktaOidc/AppAuth
2020
- RCT-Folly (2021.06.28.00-v2):
2121
- boost
@@ -402,9 +402,9 @@ SPEC CHECKSUMS:
402402
FBLazyVector: 2c76493a346ef8cacf1f442926a39f805fffec1f
403403
FBReactNativeSpec: 371350f24afa87b6aba606972ec959dcd4a95c9a
404404
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
405-
glog: 85ecdd10ee8d8ec362ef519a6a45ff9aa27b2e85
406-
OktaOidc: 7a6a9a827722b26fded69c9715e606f5abc3a3a7
407-
RCT-Folly: 803a9cfd78114b2ec0f140cfa6fa2a6bafb2d685
405+
glog: 476ee3e89abb49e07f822b48323c51c57124b572
406+
OktaOidc: 94d65a417fefbdd92441a6e8b8347e2db2ea885a
407+
RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8
408408
RCTRequired: 00581111c53531e39e3c6346ef0d2c0cf52a5a37
409409
RCTTypeSafety: 07e03ee7800e7dd65cba8e52ad0c2edb06c96604
410410
React: e61f4bf3c573d0c61c56b53dc3eb1d9daf0768a0

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,4 @@
106106
"tsd": {
107107
"directory": "./dist/types"
108108
}
109-
}
109+
}

test/index.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,23 @@ describe('OktaReactNative', () => {
148148
Platform.OS = 'ios';
149149
Platform.Version = '1.0.0';
150150
const processedScope = config.scopes.join(' ');
151+
152+
config = {
153+
...config,
154+
keychainService: 'Test',
155+
keychainAccessGroup: 'com.example.SharedItems',
156+
}
151157
createConfig(config);
158+
152159
expect(mockCreateConfig).toHaveBeenCalledTimes(1);
153160
expect(mockCreateConfig).toHaveBeenCalledWith(
154161
config.clientId,
155162
config.redirectUri,
156163
config.endSessionRedirectUri,
157164
config.discoveryUri,
158165
processedScope,
166+
config.keychainService,
167+
config.keychainAccessGroup,
159168
`okta-react-native/${version} $UPSTREAM_SDK react-native/${reactNativeVersion} ios/1.0.0`,
160169
defaultTimeouts.httpConnectionTimeout,
161170
);

types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export namespace Okta {
6363
endSessionRedirectUri: string;
6464
discoveryUri: string;
6565
scopes: string[];
66+
keychainService?: string;
67+
keychainAccessGroup?: string;
6668
requireHardwareBackedKeyStore: boolean;
6769
androidChromeTabColor?: string;
6870
httpConnectionTimeout?: number;

types/index.test-d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ expectType<Promise<boolean>>(OktaSDK.createConfig({
2323
endSessionRedirectUri: '{endSessionRedirectUri}',
2424
discoveryUri: '{discoveryUri}',
2525
scopes: ['scope1', 'scope2'],
26+
keychainService: 'Test';
27+
keychainAccessGroup: 'com.example.SharedItems';
2628
requireHardwareBackedKeyStore: false,
2729
androidChromeTabColor: '#00000',
2830
httpConnectionTimeout: 15,

0 commit comments

Comments
 (0)