Skip to content
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
8 changes: 6 additions & 2 deletions MSALiOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
4DC17E926BAC0BBF780DD200 /* Pods_MSALiOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD5263E088346F01270DB01B /* Pods_MSALiOS.framework */; };
A10000000000000000000001 /* ExternalPopKeyStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = A10000000000000000000002 /* ExternalPopKeyStore.swift */; };
FBA79EA11E9A038700179A54 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA79EA01E9A038700179A54 /* AppDelegate.swift */; };
FBA79EA31E9A038700179A54 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA79EA21E9A038700179A54 /* ViewController.swift */; };
FBA79EA61E9A038700179A54 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FBA79EA41E9A038700179A54 /* Main.storyboard */; };
Expand All @@ -17,6 +18,7 @@

/* Begin PBXFileReference section */
AD5263E088346F01270DB01B /* Pods_MSALiOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MSALiOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A10000000000000000000002 /* ExternalPopKeyStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalPopKeyStore.swift; sourceTree = "<group>"; };
C7CBF881BF504F0A15F7FD5C /* Pods-MSALiOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MSALiOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MSALiOS/Pods-MSALiOS.debug.xcconfig"; sourceTree = "<group>"; };
DF06CB9178C218430A5EDADB /* Pods-MSALiOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MSALiOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-MSALiOS/Pods-MSALiOS.release.xcconfig"; sourceTree = "<group>"; };
FBA79E9D1E9A038700179A54 /* MSALiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MSALiOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -90,6 +92,7 @@
children = (
FBBE35B41E9DAE6C00460053 /* MSALiOS.entitlements */,
FBA79EA01E9A038700179A54 /* AppDelegate.swift */,
A10000000000000000000002 /* ExternalPopKeyStore.swift */,
FBA79EA21E9A038700179A54 /* ViewController.swift */,
FBA79EA41E9A038700179A54 /* Main.storyboard */,
FBA79EA71E9A038700179A54 /* Assets.xcassets */,
Expand Down Expand Up @@ -227,6 +230,7 @@
buildActionMask = 2147483647;
files = (
FBA79EA31E9A038700179A54 /* ViewController.swift in Sources */,
A10000000000000000000001 /* ExternalPopKeyStore.swift in Sources */,
FBA79EA11E9A038700179A54 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -376,7 +380,7 @@
DEVELOPMENT_TEAM = UBF8T346G9;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = MSALiOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.identitysample.MSALiOS;
Expand All @@ -397,7 +401,7 @@
DEVELOPMENT_TEAM = UBF8T346G9;
FRAMEWORK_SEARCH_PATHS = "$(inherited)";
INFOPLIST_FILE = MSALiOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.3;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = com.microsoft.identitysample.MSALiOS;
Expand Down
87 changes: 87 additions & 0 deletions MSALiOS/ExternalPopKeyStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// Copyright (c) Microsoft Corporation.
// All rights reserved.
//
// This code is licensed under the MIT License.
//

import Foundation
import MSAL
import Security

enum ExternalPopKeyStore {
private static let keyTag = "com.microsoft.identitysample.MSALiOS.atpop.rsa"
.data(using: .utf8)!

static func loadOrCreate() throws -> MSALExternalKeyPair {
let privateKey: SecKey

if let storedKey = try loadPrivateKey() {
privateKey = storedKey
} else {
let attributes: [String: Any] = [
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeySizeInBits as String: 2048,
kSecPrivateKeyAttrs as String: [
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: keyTag
]
]

var keyCreationError: Unmanaged<CFError>?
guard let createdKey = SecKeyCreateRandomKey(
attributes as CFDictionary,
&keyCreationError
) else {
if let error = keyCreationError?.takeRetainedValue() {
throw error
}

throw NSError(
domain: NSOSStatusErrorDomain,
code: Int(errSecParam),
userInfo: [NSLocalizedDescriptionKey: "Unable to create the RSA key pair."]
)
}

privateKey = createdKey
}

guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
throw NSError(
domain: NSOSStatusErrorDomain,
code: Int(errSecDecode),
userInfo: [NSLocalizedDescriptionKey: "Unable to derive the public key."]
)
}

return try MSALExternalKeyPair(privateKey: privateKey, publicKey: publicKey)
}

private static func loadPrivateKey() throws -> SecKey? {
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
kSecAttrApplicationTag as String: keyTag,
kSecReturnRef as String: true
]

var result: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &result)

if status == errSecItemNotFound {
return nil
}

guard status == errSecSuccess, let result = result else {
throw NSError(
domain: NSOSStatusErrorDomain,
code: Int(status),
userInfo: [NSLocalizedDescriptionKey: "Unable to load the stored RSA key pair."]
)
}

return (result as! SecKey)
}
}
110 changes: 109 additions & 1 deletion MSALiOS/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class ViewController: UIViewController, UITextFieldDelegate, URLSessionDelegate
// Update the below to your client ID you received in the portal. The below is for running the demo only
let kClientID = "66855f8a-60cd-445e-a9bb-8cd8eadbd3fa"
let kGraphEndpoint = "https://graph.microsoft.com/"
let kPopEndpoint = URL(string: "https://signedhttprequest.azurewebsites.net/api/validateSHR")!
let kAuthority = "https://login.microsoftonline.com/common"
let kRedirectUri = "msauth.com.microsoft.identitysample.MSALiOS://auth"

Expand All @@ -43,10 +44,12 @@ class ViewController: UIViewController, UITextFieldDelegate, URLSessionDelegate
var accessToken = String()
var applicationContext : MSALPublicClientApplication?
var webViewParamaters : MSALWebviewParameters?
var externalPopKeyPair: MSALExternalKeyPair?

var loggingText: UITextView!
var signOutButton: UIButton!
var callGraphButton: UIButton!
var callExternalPopButton: UIButton!
var usernameLabel: UILabel!

var currentAccount: MSALAccount?
Expand Down Expand Up @@ -128,6 +131,13 @@ extension ViewController {
redirectUri: kRedirectUri,
authority: authority)
self.applicationContext = try MSALPublicClientApplication(configuration: msalConfiguration)

do {
self.externalPopKeyPair = try ExternalPopKeyStore.loadOrCreate()
} catch {
self.updateLogging(text: "Unable to load the External AT PoP key: \(error)")
}

self.initWebViewParams()
}

Expand Down Expand Up @@ -308,6 +318,88 @@ extension ViewController {
}.resume()
}

@objc func callExternalPopAPI(_ sender: UIButton) {
self.loadCurrentAccount { account in
guard let currentAccount = account else {
self.acquireExternalPopTokenInteractively()
return
}

self.acquireExternalPopTokenSilently(currentAccount)
}
}

func acquireExternalPopTokenInteractively() {
guard let applicationContext = self.applicationContext else { return }
guard let webViewParameters = self.webViewParamaters else { return }
guard let authenticationScheme = self.externalPopScheme() else { return }

let parameters = MSALInteractiveTokenParameters(
scopes: kScopes,
webviewParameters: webViewParameters
)
parameters.promptType = .selectAccount
parameters.authenticationScheme = authenticationScheme

applicationContext.acquireToken(with: parameters) { result, error in
guard let result = result else {
self.updateLogging(text: "Could not acquire External AT PoP token: \(String(describing: error))")
return
}

self.accessToken = result.accessToken
self.updateCurrentAccount(account: result.account)
self.updateLogging(
text: "External AT PoP token acquired with key ID \(authenticationScheme.externalKeyPair?.keyId ?? "unknown")."
)
}
}

func acquireExternalPopTokenSilently(_ account: MSALAccount) {
guard let applicationContext = self.applicationContext else { return }
guard let authenticationScheme = self.externalPopScheme() else { return }

let parameters = MSALSilentTokenParameters(scopes: kScopes, account: account)
parameters.authenticationScheme = authenticationScheme

applicationContext.acquireTokenSilent(with: parameters) { result, error in
if let nsError = error as NSError?,
nsError.domain == MSALErrorDomain,
nsError.code == MSALError.interactionRequired.rawValue {
DispatchQueue.main.async {
self.acquireExternalPopTokenInteractively()
}
return
}

guard let result = result else {
self.updateLogging(text: "Could not acquire External AT PoP token silently: \(String(describing: error))")
return
}

self.accessToken = result.accessToken
self.updateSignOutButton(enabled: true)
self.updateLogging(
text: "External AT PoP token acquired silently with key ID \(authenticationScheme.externalKeyPair?.keyId ?? "unknown")."
)
}
}

func externalPopScheme() -> MSALAuthenticationSchemePop? {
guard let keyPair = self.externalPopKeyPair else {
self.updateLogging(text: "External AT PoP key is unavailable.")
return nil
}

return MSALAuthenticationSchemePop(
httpMethod: .POST,
request: kPopEndpoint,
nonce: nil,
additionalParameters: nil,
externalKeyPair: keyPair
)
}

}


Expand Down Expand Up @@ -460,6 +552,22 @@ extension ViewController {
callGraphButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 120.0).isActive = true
callGraphButton.widthAnchor.constraint(equalToConstant: 300.0).isActive = true
callGraphButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true

callExternalPopButton = UIButton()
callExternalPopButton.translatesAutoresizingMaskIntoConstraints = false
callExternalPopButton.setTitle("Acquire External AT PoP Token", for: .normal)
callExternalPopButton.setTitleColor(.blue, for: .normal)
callExternalPopButton.addTarget(
self,
action: #selector(callExternalPopAPI(_:)),
for: .touchUpInside
)
self.view.addSubview(callExternalPopButton)

callExternalPopButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
callExternalPopButton.topAnchor.constraint(equalTo: callGraphButton.bottomAnchor, constant: 10.0).isActive = true
callExternalPopButton.widthAnchor.constraint(equalToConstant: 300.0).isActive = true
callExternalPopButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true

// Add sign out button
signOutButton = UIButton()
Expand All @@ -471,7 +579,7 @@ extension ViewController {
self.view.addSubview(signOutButton)

signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
signOutButton.topAnchor.constraint(equalTo: callGraphButton.bottomAnchor, constant: 10.0).isActive = true
signOutButton.topAnchor.constraint(equalTo: callExternalPopButton.bottomAnchor, constant: 10.0).isActive = true
signOutButton.widthAnchor.constraint(equalToConstant: 150.0).isActive = true
signOutButton.heightAnchor.constraint(equalToConstant: 50.0).isActive = true

Expand Down
28 changes: 18 additions & 10 deletions Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
PODS:
- MSAL (1.1.3):
- MSAL/app-lib (= 1.1.3)
- MSAL/app-lib (1.1.3)
- MSAL (2.15.0):
- MSAL/app-lib (= 2.15.0)
- MSAL/app-lib (2.15.0)

DEPENDENCIES:
- MSAL
- MSAL (from `https://github.com/AzureAD/microsoft-authentication-library-for-objc.git`, branch `dev`)

SPEC REPOS:
trunk:
- MSAL
EXTERNAL SOURCES:
MSAL:
:branch: dev
:git: https://github.com/AzureAD/microsoft-authentication-library-for-objc.git
:submodules: true

CHECKOUT OPTIONS:
MSAL:
:commit: 830120587c0c6791baa34f3116d7fdc5b33e5d6b
:git: https://github.com/AzureAD/microsoft-authentication-library-for-objc.git
:submodules: true

SPEC CHECKSUMS:
MSAL: 0539cb503a2a279365181bf111071b4b32071177
MSAL: dc3431b88df557da342e1eb2bd6f05102bddf6ac

PODFILE CHECKSUM: 37c6710cfa48c2a9cbf83d25acbf301323d86266
PODFILE CHECKSUM: de3a209e156784ae5f758cf74287bee4212fb2c3

COCOAPODS: 1.9.1
COCOAPODS: 1.17.0
7 changes: 5 additions & 2 deletions podfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use_frameworks!

platform :ios, '11.0'
platform :ios, '17.0'

target 'MSALiOS' do
pod 'MSAL'
pod 'MSAL',
:git => 'https://github.com/AzureAD/microsoft-authentication-library-for-objc.git',
:branch => 'dev',
:submodules => true
end