Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Store device secret in keychain #304

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export const createConfig = async({
endSessionRedirectUri,
discoveryUri,
scopes,
keychainService = 'Okta',
keychainAccessGroup = 'com.okta.mobile-sdk.shared',
requireHardwareBackedKeyStore,
androidChromeTabColor,
httpConnectionTimeout,
Expand Down Expand Up @@ -110,6 +112,8 @@ export const createConfig = async({
endSessionRedirectUri,
discoveryUri,
scopes,
keychainService,
keychainAccessGroup,
userAgentTemplate,
httpConnectionTimeout,
);
Expand Down
47 changes: 46 additions & 1 deletion ios/OktaSdkBridge/OktaSdkBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()
Expand All @@ -85,6 +118,8 @@ class OktaSdkBridge: RCTEventEmitter {
endSessionRedirectUri: String,
discoveryUri: String,
scopes: String,
keychainService: String,
keychainAccessGroup: String,
userAgentTemplate: String,
requestTimeout: Int,
promiseResolver: RCTPromiseResolveBlock,
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions ios/OktaSdkBridge/ReactNativeOktaSdkBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,4 @@
"tsd": {
"directory": "./dist/types"
}
}
}
9 changes: 9 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,23 @@ 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,
config.redirectUri,
config.endSessionRedirectUri,
config.discoveryUri,
processedScope,
config.keychainService,
config.keychainAccessGroup,
`okta-react-native/${version} $UPSTREAM_SDK react-native/${reactNativeVersion} ios/1.0.0`,
defaultTimeouts.httpConnectionTimeout,
);
Expand Down
2 changes: 2 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export namespace Okta {
endSessionRedirectUri: string;
discoveryUri: string;
scopes: string[];
keychainService?: string;
keychainAccessGroup?: string;
requireHardwareBackedKeyStore: boolean;
androidChromeTabColor?: string;
httpConnectionTimeout?: number;
Expand Down
2 changes: 2 additions & 0 deletions types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ expectType<Promise<boolean>>(OktaSDK.createConfig({
endSessionRedirectUri: '{endSessionRedirectUri}',
discoveryUri: '{discoveryUri}',
scopes: ['scope1', 'scope2'],
keychainService: 'Test';
keychainAccessGroup: 'com.example.SharedItems';
requireHardwareBackedKeyStore: false,
androidChromeTabColor: '#00000',
httpConnectionTimeout: 15,
Expand Down