다음을 통해 공유


자습서: iOS(Swift) 모바일 앱에서 사용자 로그인

적용 대상: 흰색 확인 표시 기호가 있는 녹색 원. Workforce 테넌트 흰색 확인 표시 기호가 있는 녹색 원. 외부 테넌트 (자세히 알아보기)

Microsoft Entra ID를 사용하여 사용자 로그인을 안내하는 자습서 시리즈의 세 번째 자습서입니다.

시작하기 전에 이 페이지의 맨 위에 있는 테넌트 유형 선택기를 선택하여 테넌트 유형을 선택합니다. Microsoft Entra ID는 두 가지 테넌트 구성, 직원외부를 제공합니다. 직원 테넌트 구성은 직원, 내부 앱 및 기타 조직 리소스를 위한 것입니다. 외부 테넌트는 고객용 앱용입니다.

이 자습서에서는 다음을 수행합니다.

  • 사용자 로그인
  • 사용자를 로그아웃합니다.
  • 앱의 UI 만들기

필수 구성 요소

사용자 로그인

iOS용 MSAL(Microsoft 인증 라이브러리)을 사용하여 사용자를 로그인하기 위한 두 가지 주요 옵션이 있습니다. 즉, 대화형 또는 자동으로 토큰을 획득합니다.

  1. 대화형으로 사용자를 로그인하려면 다음 코드를 사용합니다.

    func acquireTokenInteractively() {
    
        guard let applicationContext = self.applicationContext else { return }
        guard let webViewParameters = self.webViewParameters else { return }
    
        // #1
        let parameters = MSALInteractiveTokenParameters(scopes: kScopes, webviewParameters: webViewParameters)
        parameters.promptType = .selectAccount
    
        // #2
        applicationContext.acquireToken(with: parameters) { (result, error) in
    
            // #3
            if let error = error {
    
                self.updateLogging(text: "Could not acquire token: \(error)")
                return
            }
    
            guard let result = result else {
    
                self.updateLogging(text: "Could not acquire token: No result returned")
                return
            }
    
            // #4
            self.accessToken = result.accessToken
            self.updateLogging(text: "Access token is \(self.accessToken)")
            self.updateCurrentAccount(account: result.account)
            self.getContentWithToken()
        }
    }
    

    promptType MSALInteractiveTokenParameters 속성은 인증 및 동의 프롬프트 동작을 구성합니다. 지원되는 값은 다음과 같습니다.

    • .promptIfNecessary(기본값) - 필요한 경우에만 사용자에게 메시지가 표시됩니다. SSO 환경은 웹 보기에 쿠키가 있는지와 계정 유형에 따라 결정됩니다. 여러 사용자가 로그인하면 계정 선택 환경이 표시됩니다. 기본 동작입니다.
    • .selectAccount - 사용자가 지정되지 않은 경우 인증 웹 보기는 사용자가 선택할 수 있도록 현재 로그인한 계정 목록을 표시합니다.
    • .login - 사용자가 웹 보기에서 인증해야 합니다. 이 값을 지정하는 경우 한 번에 하나의 계정만 로그인할 수 있습니다.
    • .consent - 사용자가 요청에 대한 현재 범위 집합에 동의하도록 요구합니다.
  2. 사용자를 조용히 로그인시키려면 다음 코드를 사용합니다.

    
        func acquireTokenSilently(_ account : MSALAccount!) {
    
            guard let applicationContext = self.applicationContext else { return }
    
            /**
    
             Acquire a token for an existing account silently
    
             - forScopes:           Permissions you want included in the access token received
             in the result in the completionBlock. Not all scopes are
             guaranteed to be included in the access token returned.
             - account:             An account object that we retrieved from the application object before that the
             authentication flow will be locked down to.
             - completionBlock:     The completion block that will be called when the authentication
             flow completes, or encounters an error.
             */
    
            let parameters = MSALSilentTokenParameters(scopes: kScopes, account: account)
    
            applicationContext.acquireTokenSilent(with: parameters) { (result, error) in
    
                if let error = error {
    
                    let nsError = error as NSError
    
                    // interactionRequired means we need to ask the user to sign-in. This usually happens
                    // when the user's Refresh Token is expired or if the user has changed their password
                    // among other possible reasons.
    
                    if (nsError.___domain == MSALErrorDomain) {
    
                        if (nsError.code == MSALError.interactionRequired.rawValue) {
    
                            DispatchQueue.main.async {
                                self.acquireTokenInteractively()
                            }
                            return
                        }
                    }
    
                    self.updateLogging(text: "Could not acquire token silently: \(error)")
                    return
                }
    
                guard let result = result else {
    
                    self.updateLogging(text: "Could not acquire token: No result returned")
                    return
                }
    
                self.accessToken = result.accessToken
                self.updateLogging(text: "Refreshed Access token is \(self.accessToken)")
                self.updateSignOutButton(enabled: true)
                self.getContentWithToken()
            }
        }
    

    acquireTokenSilently 메서드는 기존 MSAL 계정에 대한 액세스 토큰을 자동으로 획득하려고 시도합니다. applicationContext 사용하여 지정된 범위의 토큰을 요청합니다. 오류가 발생하면 사용자 상호 작용이 필요한지 확인하고, 필요한 경우 대화형 토큰 획득을 시작합니다. 성공하면 액세스 토큰을 업데이트하고, 결과를 기록하고, 로그아웃 단추를 사용하도록 설정하고, 토큰을 사용하여 콘텐츠를 검색합니다.

로그인 콜백 처리(iOS만 해당)

AppDelegate.swift 파일을 엽니다. 로그인 후 콜백을 처리하려면 다음과 같이 MSALPublicClientApplication.handleMSALResponse 클래스에 appDelegate 추가합니다.

// Inside AppDelegate...
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {

        return MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String)
}

Xcode 11사용하는 경우 대신 MSAL 콜백을 SceneDelegate.swift 배치해야 합니다. 이전 iOS와의 호환성을 위해 UISceneDelegate와 UIApplicationDelegate를 모두 지원하는 경우 MSAL 콜백을 두 파일에 모두 배치해야 합니다.

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {

        guard let urlContext = URLContexts.first else {
            return
        }

        let url = urlContext.url
        let sourceApp = urlContext.options.sourceApplication

        MSALPublicClientApplication.handleMSALResponse(url, sourceApplication: sourceApp)
    }

사용자 로그아웃

중요하다

MSAL을 사용하여 로그아웃하면 디바이스 구성에서 허용되는 경우 디바이스에서 활성 세션을 제거할 뿐만 아니라 애플리케이션에서 사용자에 대한 알려진 모든 정보가 제거됩니다. 필요에 따라 브라우저에서 사용자를 로그아웃할 수도 있습니다.

로그아웃 기능을 추가하려면 ViewController 클래스 내에 다음 코드를 추가합니다.

@objc func signOut(_ sender: AnyObject) {

        guard let applicationContext = self.applicationContext else { return }

        guard let account = self.currentAccount else { return }

        do {

            /**
             Removes all tokens from the cache for this application for the provided account

             - account:    The account to remove from the cache
             */

            let signoutParameters = MSALSignoutParameters(webviewParameters: self.webViewParameters!)
            signoutParameters.signoutFromBrowser = false // set this to true if you also want to signout from browser or webview

            applicationContext.signout(with: account, signoutParameters: signoutParameters, completionBlock: {(success, error) in

                if let error = error {
                    self.updateLogging(text: "Couldn't sign out account with error: \(error)")
                    return
                }

                self.updateLogging(text: "Sign out completed successfully")
                self.accessToken = ""
                self.updateCurrentAccount(account: nil)
            })

        }
    }

앱의 UI 만들기

이제 다음 코드를 ViewController 클래스에 추가하여 Microsoft Graph API를 호출하는 단추, 로그아웃할 다른 항목 및 일부 출력을 볼 수 있는 텍스트 뷰를 포함하는 UI를 만듭니다.

iOS 사용자 인터페이스 (iOS UI)

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

func initUI() {

    usernameLabel = UILabel()
    usernameLabel.translatesAutoresizingMaskIntoConstraints = false
    usernameLabel.text = ""
    usernameLabel.textColor = .darkGray
    usernameLabel.textAlignment = .right

    self.view.addSubview(usernameLabel)

    usernameLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 50.0).isActive = true
    usernameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10.0).isActive = true
    usernameLabel.widthAnchor.constraint(equalToConstant: 300.0).isActive = true
    usernameLabel.heightAnchor.constraint(equalToConstant: 50.0).isActive = true

    // Add call Graph button
    callGraphButton  = UIButton()
    callGraphButton.translatesAutoresizingMaskIntoConstraints = false
    callGraphButton.setTitle("Call Microsoft Graph API", for: .normal)
    callGraphButton.setTitleColor(.blue, for: .normal)
    callGraphButton.addTarget(self, action: #selector(callGraphAPI(_:)), for: .touchUpInside)
    self.view.addSubview(callGraphButton)

    callGraphButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    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

    // Add sign out button
    signOutButton = UIButton()
    signOutButton.translatesAutoresizingMaskIntoConstraints = false
    signOutButton.setTitle("Sign Out", for: .normal)
    signOutButton.setTitleColor(.blue, for: .normal)
    signOutButton.setTitleColor(.gray, for: .disabled)
    signOutButton.addTarget(self, action: #selector(signOut(_:)), for: .touchUpInside)
    self.view.addSubview(signOutButton)

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

    let deviceModeButton = UIButton()
    deviceModeButton.translatesAutoresizingMaskIntoConstraints = false
    deviceModeButton.setTitle("Get device info", for: .normal);
    deviceModeButton.setTitleColor(.blue, for: .normal);
    deviceModeButton.addTarget(self, action: #selector(getDeviceMode(_:)), for: .touchUpInside)
    self.view.addSubview(deviceModeButton)

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

    // Add logging textfield
    loggingText = UITextView()
    loggingText.isUserInteractionEnabled = false
    loggingText.translatesAutoresizingMaskIntoConstraints = false

    self.view.addSubview(loggingText)

    loggingText.topAnchor.constraint(equalTo: deviceModeButton.bottomAnchor, constant: 10.0).isActive = true
    loggingText.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10.0).isActive = true
    loggingText.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0).isActive = true
    loggingText.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 10.0).isActive = true
}

func platformViewDidLoadSetup() {

    NotificationCenter.default.addObserver(self,
                        selector: #selector(appCameToForeGround(notification:)),
                        name: UIApplication.willEnterForegroundNotification,
                        object: nil)

}

@objc func appCameToForeGround(notification: Notification) {
    self.loadCurrentAccount()
}

macOS 사용자 인터페이스


var callGraphButton: NSButton!
var loggingText: NSTextView!
var signOutButton: NSButton!

var usernameLabel: NSTextField!

func initUI() {

    usernameLabel = NSTextField()
    usernameLabel.translatesAutoresizingMaskIntoConstraints = false
    usernameLabel.stringValue = ""
    usernameLabel.isEditable = false
    usernameLabel.isBezeled = false
    self.view.addSubview(usernameLabel)

    usernameLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 30.0).isActive = true
    usernameLabel.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10.0).isActive = true

    // Add call Graph button
    callGraphButton  = NSButton()
    callGraphButton.translatesAutoresizingMaskIntoConstraints = false
    callGraphButton.title = "Call Microsoft Graph API"
    callGraphButton.target = self
    callGraphButton.action = #selector(callGraphAPI(_:))
    callGraphButton.bezelStyle = .rounded
    self.view.addSubview(callGraphButton)

    callGraphButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    callGraphButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 50.0).isActive = true
    callGraphButton.heightAnchor.constraint(equalToConstant: 34.0).isActive = true

    // Add sign out button
    signOutButton = NSButton()
    signOutButton.translatesAutoresizingMaskIntoConstraints = false
    signOutButton.title = "Sign Out"
    signOutButton.target = self
    signOutButton.action = #selector(signOut(_:))
    signOutButton.bezelStyle = .texturedRounded
    self.view.addSubview(signOutButton)

    signOutButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    signOutButton.topAnchor.constraint(equalTo: callGraphButton.bottomAnchor, constant: 10.0).isActive = true
    signOutButton.heightAnchor.constraint(equalToConstant: 34.0).isActive = true
    signOutButton.isEnabled = false

    // Add logging textfield
    loggingText = NSTextView()
    loggingText.translatesAutoresizingMaskIntoConstraints = false

    self.view.addSubview(loggingText)

    loggingText.topAnchor.constraint(equalTo: signOutButton.bottomAnchor, constant: 10.0).isActive = true
    loggingText.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10.0).isActive = true
    loggingText.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0).isActive = true
    loggingText.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -10.0).isActive = true
    loggingText.widthAnchor.constraint(equalToConstant: 500.0).isActive = true
    loggingText.heightAnchor.constraint(equalToConstant: 300.0).isActive = true
}

func platformViewDidLoadSetup() {}

다음으로, ViewController 클래스 내에서 viewDidLoad() 메서드를 다음으로 바꿉 있습니다.

    override func viewDidLoad() {

        super.viewDidLoad()

        initUI()

        do {
            try self.initMSAL()
        } catch let error {
            self.updateLogging(text: "Unable to create Application Context \(error)")
        }

        self.loadCurrentAccount()
        self.platformViewDidLoadSetup()
    }

다음 단계

자습서: iOS(Swift) 앱 보호된 웹 API 호출

Microsoft Entra ID를 사용하여 사용자 로그인을 안내하는 자습서 시리즈의 세 번째 자습서입니다.

시작하기 전에 이 페이지의 맨 위에 있는 테넌트 유형 선택기를 선택하여 테넌트 유형을 선택합니다. Microsoft Entra ID는 두 가지 테넌트 구성, 직원외부를 제공합니다. 직원 테넌트 구성은 직원, 내부 앱 및 기타 조직 리소스를 위한 것입니다. 외부 테넌트는 고객용 앱용입니다.

이 자습서에서는 다음을 수행합니다.

  • 사용자 로그인
  • 사용자를 로그아웃합니다.

필수 구성 요소

사용자 로그인

iOS용 MSAL(Microsoft 인증 라이브러리)을 사용하여 사용자를 로그인하기 위한 두 가지 주요 옵션이 있습니다. 즉, 대화형 또는 자동으로 토큰을 획득합니다.

  1. 대화형으로 사용자를 로그인하려면 다음 코드를 사용합니다.

    acquireTokenInteractively() {
        guard let applicationContext = self.applicationContext else { return }
        guard let webViewParameters = self.webViewParameters else { return }
    
        updateLogging(text: "Acquiring token interactively...")
    
        let parameters = MSALInteractiveTokenParameters(scopes: Configuration.kScopes, webviewParameters: webViewParameters)
        parameters.promptType = .selectAccount
    
        applicationContext.acquireToken(with: parameters) { (result, error) in
    
            if let error = error {
    
                self.updateLogging(text: "Could not acquire token: \(error)")
                return
            }
    
            guard let result = result else {
    
                self.updateLogging(text: "Could not acquire token: No result returned")
                return
            }
    
            self.accessToken = result.accessToken
            self.updateLogging(text: "Access token is \(self.accessToken)")
            self.updateCurrentAccount(account: result.account)
        }
    }
    

    코드는 먼저 애플리케이션 컨텍스트 및 웹 보기 매개 변수를 사용할 수 있는지 확인합니다. 그런 다음 대화형으로 토큰을 획득하고 있음을 나타내도록 로깅을 업데이트합니다. 다음으로, 대화형 토큰 획득을 위한 매개 변수를 설정하고 범위 및 웹 보기 매개 변수를 지정합니다. 또한 계정을 선택하도록 프롬프트 유형을 설정합니다.

    그 후 정의된 매개 변수를 사용하여 애플리케이션 컨텍스트에서 acquireToken 메서드를 호출합니다. 완료 처리기에서 오류를 확인합니다. 오류가 발생하면 로깅을 오류 메시지로 업데이트합니다. 성공하면 결과에서 액세스 토큰을 검색하고, 로깅을 토큰으로 업데이트하고, 현재 계정을 업데이트합니다.

    앱이 액세스 토큰을 획득하면 현재 계정과 연결된 클레임을 검색할 수 있습니다. 이렇게 하려면 다음 코드 조각을 사용합니다.

    let claims = result.account.accountClaims
    let preferredUsername = claims?["preferred_username"] as? String
    

    이 코드는 accountClaims 개체의 result.account 속성에 액세스하여 계정에서 클레임을 읽습니다. 그런 다음 클레임 사전에서 "preferred_username" 클레임의 값을 검색하고 preferredUsername 변수에 할당합니다.

  2. 사용자를 조용히 로그인시키려면 다음 코드를 사용합니다.

    func acquireTokenSilently() {
        self.loadCurrentAccount { (account) in
    
            guard let currentAccount = account else {
    
                self.updateLogging(text: "No token found, try to acquire a token interactively first")
                return
            }
    
            self.acquireTokenSilently(currentAccount)
        }
    }
    

    이 코드는 토큰을 자동으로 획득하는 프로세스를 시작합니다. 먼저 현재 계정을 로드하려고 시도합니다. 현재 계정이 발견되면 해당 계정을 사용하여 토큰을 자동으로 획득합니다. 현재 계정이 없으면 로깅을 업데이트하여 토큰을 찾을 수 없음을 나타내고 먼저 대화형으로 토큰을 획득하도록 제안합니다.

    위의 코드에서는 loadCurrentAccountacquireTokenSilently두 개의 함수를 호출합니다. loadCurrentAccount 함수에는 다음 코드가 있어야 합니다.

    func loadCurrentAccount(completion: AccountCompletion? = nil) {
    
        guard let applicationContext = self.applicationContext else { return }
    
        let msalParameters = MSALParameters()
        msalParameters.completionBlockQueue = DispatchQueue.main
    
        // Note that this sample showcases an app that signs in a single account at a time
        applicationContext.getCurrentAccount(with: msalParameters, completionBlock: { (currentAccount, previousAccount, error) in
    
            if let error = error {
                self.updateLogging(text: "Couldn't query current account with error: \(error)")
                return
            }
    
            if let currentAccount = currentAccount {
    
                self.updateCurrentAccount(account: currentAccount)
                self.acquireTokenSilently(currentAccount)
    
                if let completion = completion {
                    completion(self.currentAccount)
                }
    
                return
            }
    
            // If testing with Microsoft's shared device mode, see the account that has been signed out from another app. More details here:
            // https://docs.microsoft.com/azure/active-directory/develop/msal-ios-shared-devices
            if let previousAccount = previousAccount {
    
                self.updateLogging(text: "The account with username \(String(describing: previousAccount.username)) has been signed out.")
    
            } else {
    
                self.updateLogging(text: "")
            }
    
            self.accessToken = ""
            self.updateCurrentAccount(account: nil)
    
            if let completion = completion {
                completion(nil)
            }
        })
    }
    

    이 코드는 iOS용 MSAL을 사용하여 현재 계정을 로드합니다. 오류를 확인하고 그에 따라 로깅을 업데이트합니다. 현재 계정이 발견되면 계정을 업데이트하고 조용히 토큰을 획득하려고 시도합니다. 이전 계정이 있는 경우 로그아웃을 기록합니다. 계정이 없으면 액세스 토큰을 지웁니다. 마지막으로 제공된 경우 완료 블록을 실행합니다.

    acquireTokenSilently 함수에는 다음 코드가 포함되어야 합니다.

    func acquireTokenSilently(_ account : MSALAccount) {
        guard let applicationContext = self.applicationContext else { return }
    
        /**
    
         Acquire a token for an existing account silently
    
         - forScopes:           Permissions you want included in the access token received
         in the result in the completionBlock. Not all scopes are
         guaranteed to be included in the access token returned.
         - account:             An account object that we retrieved from the application object before that the
         authentication flow will be locked down to.
         - completionBlock:     The completion block that will be called when the authentication
         flow completes, or encounters an error.
         */
    
        updateLogging(text: "Acquiring token silently...")
    
        let parameters = MSALSilentTokenParameters(scopes: Configuration.kScopes, account: account)
    
        applicationContext.acquireTokenSilent(with: parameters) { (result, error) in
    
            if let error = error {
    
                let nsError = error as NSError
    
                // interactionRequired means we need to ask the user to sign-in. This usually happens
                // when the user's Refresh Token is expired or if the user has changed their password
                // among other possible reasons.
    
                if (nsError.___domain == MSALErrorDomain) {
    
                    if (nsError.code == MSALError.interactionRequired.rawValue) {
    
                        DispatchQueue.main.async {
                            self.acquireTokenInteractively()
                        }
                        return
                    }
                }
    
                self.updateLogging(text: "Could not acquire token silently: \(error)")
                return
            }
    
            guard let result = result else {
    
                self.updateLogging(text: "Could not acquire token: No result returned")
                return
            }
    
            self.accessToken = result.accessToken
            self.updateLogging(text: "Refreshed Access token is \(self.accessToken)")
            self.updateSignOutButton(enabled: true)
        }
    }
    
    

    이 함수는 iOS용 MSAL을 사용하여 기존 계정에 대한 토큰을 자동으로 획득합니다. applicationContext확인한 후 토큰 획득 프로세스를 기록합니다. MSALSilentTokenParameters사용하여 필요한 매개 변수를 정의합니다. 그런 다음 조용히 토큰을 획득하려고 시도합니다. 오류가 있는 경우 사용자 상호 작용 요구 사항을 확인하고 필요한 경우 대화형 프로세스를 시작합니다. 성공하면 accessToken 속성을 업데이트하고 새로 고친 토큰을 기록하며 로그아웃 단추를 사용하도록 설정하여 결론을 내립니다.

사용자 로그아웃

iOS용 MSAL을 사용하여 iOS(Swift) 앱에서 사용자를 로그아웃하려면 다음 코드를 사용합니다.

   @IBAction func signOut(_ sender: UIButton) {

        guard let applicationContext = self.applicationContext else { return }

        guard let account = self.currentAccount else { return }

        guard let webViewParameters = self.webViewParameters else { return }

        updateLogging(text: "Signing out...")

        do {

            /**
             Removes all tokens from the cache for this application for the provided account

             - account:    The account to remove from the cache
             */

            let signoutParameters = MSALSignoutParameters(webviewParameters: webViewParameters)

            // If testing with Microsoft's shared device mode, trigger signout from browser. More details here:
            // https://docs.microsoft.com/azure/active-directory/develop/msal-ios-shared-devices

            if (self.currentDeviceMode == .shared) {
                signoutParameters.signoutFromBrowser = true
            } else {
                signoutParameters.signoutFromBrowser = false
            }

            applicationContext.signout(with: account, signoutParameters: signoutParameters, completionBlock: {(success, error) in

                if let error = error {
                    self.updateLogging(text: "Couldn't sign out account with error: \(error)")
                    return
                }

                self.updateLogging(text: "Sign out completed successfully")
                self.accessToken = ""
                self.updateCurrentAccount(account: nil)
            })

        }
    }

이 코드는 applicationContext, currentAccountwebViewParameters있는지 확인합니다. 그런 다음 로그아웃 프로세스를 기록합니다. 이 코드는 제공된 계정에 대한 캐시에서 모든 토큰을 제거합니다. 현재 디바이스 모드에 따라 브라우저에서 로그아웃할지 여부를 결정합니다. 완료되면 그에 따라 로깅 텍스트를 업데이트합니다. 로그아웃 프로세스 중에 오류가 발생하면 오류 메시지를 기록합니다. 로그아웃에 성공하면 액세스 토큰을 빈 문자열로 업데이트하고 현재 계정을 지웁니다.

다음 단계

자습서: iOS(Swift) 앱 보호된 웹 API 호출