Tutoriel : appeler plusieurs API dans une application iOS/macOS en utilisant l’authentification native

S’applique à : cercle vert avec un symbole de coche blanc qui indique que le contenu suivant s’applique aux locataires externes. Locataires externes (en savoir plus)

Dans ce tutoriel, vous allez apprendre à acquérir un jeton d’accès et à appeler une API dans votre application iOS/macOS. Le Kit de développement logiciel (SDK) d’authentification native de la bibliothèque d’authentification Microsoft (MSAL) pour iOS/macOS vous permet d’acquérir plusieurs jetons d’accès avec une authentification unique. Cette fonctionnalité vous permet d’acquérir un ou plusieurs jetons d’accès sans demander à un utilisateur de se réauthentifier.

Dans ce tutoriel, vous allez :

  • Acquérir un ou plusieurs jetons d’accès.
  • Appeler une API

Conditions préalables

Acquérir un ou plusieurs jetons d’accès

Le Kit de développement logiciel (SDK) d’authentification native de MSAL peut stocker plusieurs jetons d’accès. Après vous être connecté, vous pouvez obtenir un jeton d’accès à l’aide de la fonction getAccessToken(parameters:) et spécifier les étendues du nouveau jeton d’accès que vous souhaitez accorder.

  1. Déclarez et définissez des valeurs pour un ensemble d’étendues d’API à l’aide de l’extrait de code suivant :

    let protectedAPIUrl1: String? = nil
    let protectedAPIUrl2: String? = nil 
    let protectedAPIScopes1: [String] = []
    let protectedAPIScopes2: [String] = []
    
    var accessTokenAPI1: String?
    var accessTokenAPI2: String?
    
    • Initialisez protectedAPIUrl1 avec l’URL de votre première API web.
    • Initialisez protectedAPIUrl2 avec l’URL de votre deuxième API web.
    • Définissez protectedAPIScopes1 avec des périmètres pour votre première API, comme ["api://<Resource_App_ID>/ToDoList.Read", "api://<Resource_App_ID>/ToDoList.ReadWrite"].
    • Définissez protectedAPIScopes2 avec des périmètres pour votre deuxième API, similaire à protectedAPIScopes1.
    • Déclarez les variables de chaîne facultatives accessTokenAPI1 et accessTokenAPI2.
  2. Connecte l’utilisateur à l’aide de l’extrait de code suivant :

    @IBAction func signInPressed(_: Any) {
        guard let email = emailTextField.text, let password = passwordTextField.text else {
            resultTextView.text = "Email or password not set"
            return
        }
    
        print("Signing in with email \(email) and password")
    
        showResultText("Signing in...")
        let parameters = MSALNativeAuthSignInParameters(username: email)
        parameters.password = password
        nativeAuth.signIn(parameters: parameters, delegate: self)
    }
    

    La méthode signInPressed gère l’appui sur le bouton de connexion. Elle vérifie si les champs d’e-mail et de mot de passe sont remplis. Si l’un ou l’autre est vide, elle affiche E-mail ou mot de passe non défini. Si les deux champs sont remplis, elle enregistre l’e-mail, affiche Connexion en cours... et lance la connexion à l’aide de la méthode signIn à partir de nativeAuth avec l’e-mail et le mot de passe fournis. Le Kit de développement logiciel (SDK) récupère un jeton valide pour les étendues OIDC par défaut (openid, offline_access, profile), car aucune étendue n’est spécifiée.

  3. Acquérir un ou plusieurs jetons d’accès à l’aide de l’extrait de code suivant :

    @IBAction func protectedApi1Pressed(_: Any) {
        guard let url = protectedAPIUrl1, !protectedAPIScopes1.isEmpty else {
            showResultText("API 1 not configured.")
            return
        }
    
        if let accessToken = accessTokenAPI1 {
            accessProtectedAPI(apiUrl: url, accessToken: accessToken)
        } else {
            let parameters = MSALNativeAuthGetAccessTokenParameters()
            parameters.scopes = protectedAPIScopes1
            accountResult?.getAccessToken(parameters: parameters, delegate: self)
            let message = "Retrieving access token to use with API 1..."
            showResultText(message)
            print(message)
        }
    }
    
    @IBAction func protectedApi2Pressed(_: Any) {
        guard let url = protectedAPIUrl2, !protectedAPIScopes2.isEmpty else {
            showResultText("API 2 not configured.")
            return
        }
    
        if let accessToken = accessTokenAPI2 {
            accessProtectedAPI(apiUrl: url, accessToken: accessToken)
        } else {
            let parameters = MSALNativeAuthGetAccessTokenParameters()
            parameters.scopes = protectedAPIScopes2
            accountResult?.getAccessToken(parameters: parameters, delegate: self)
            let message = "Retrieving access token to use with API 2..."
            showResultText(message)
            print(message)
        }
    }
    

    Les méthodes protectedApi1Pressed et protectedApi2Pressed gèrent le processus d’acquisition de jetons d’accès pour deux ensembles distincts d’étendues. Elles s’assurent d’abord que l’URL et les étendues de chaque API sont correctement configurées. Si un jeton d’accès pour l’API est déjà disponible, elle accède directement à l’API. Sinon, elle demande un jeton d’accès et informe l’utilisateur du processus de récupération de jeton en cours.

    Pour affecter un jeton d’accès à protectedAPIScopes1 et protectedAPIScopes2, utilisez l’extrait suivant :

    func onAccessTokenRetrieveCompleted(result: MSALNativeAuthTokenResult) {
        print("Access Token: \(result.accessToken)")
    
        if protectedAPIScopes1.allSatisfy(result.scopes.contains),
           let url = protectedAPIUrl1
        {
            accessTokenAPI1 = result.accessToken
            accessProtectedAPI(apiUrl: url, accessToken: result.accessToken)
        }
    
        if protectedAPIScopes2.allSatisfy(result.scopes.contains(_:)),
           let url = protectedAPIUrl2
        {
            accessTokenAPI2 = result.accessToken
            accessProtectedAPI(apiUrl: url, accessToken: result.accessToken)
        }
    
        showResultText("Signed in." + "\n\n" + "Scopes:\n\(result.scopes)" + "\n\n" + "Access Token:\n\(result.accessToken)")
        updateUI()
    }
    
    func onAccessTokenRetrieveError(error: MSAL.RetrieveAccessTokenError) {
        showResultText("Error retrieving access token: \(error.errorDescription ?? "No error description")")
    }
    

    La méthode onAccessTokenRetrieveCompleted affiche le jeton d’accès dans la console. Elle vérifie ensuite si des protectedAPIScopes1 sont incluses dans les étendues du résultat et si une protectedAPIUrl1 est disponible. Si c’est le cas, elle définit l’accessTokenAPI1 et appelle l’accessProtectedAPI avec l’URL et le jeton. Elle effectue une vérification similaire pour protectedAPIScopes2 et protectedAPIUrl2 en mettant à jour l’accessTokenAPI2 et en appelant l’API si les conditions sont remplies. Pour finir, la méthode affiche un message avec l’état de connexion, les étendues et le jeton d’accès, et met à jour l’interface utilisateur.

    La méthode onAccessTokenRetrieveError affiche un message d’erreur avec la description de l’erreur de récupération du jeton d’accès ou un message par défaut si aucune description n’est fournie.

Appeler une API

Utilisez les extraits de code suivants pour appeler une API :

func accessProtectedAPI(apiUrl: String, accessToken: String) {
    guard let url = URL(string: apiUrl) else {
        let errorMessage = "Invalid API url"
        print(errorMessage)
        DispatchQueue.main.async {
            self.showResultText(errorMessage)
        }
        return
    }
    
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            print("Error found when accessing API: \(error.localizedDescription)")
            DispatchQueue.main.async {
                self.showResultText(error.localizedDescription)
            }
            return
        }
        
        guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode)
        else {
            DispatchQueue.main.async {
                self.showResultText("Unsuccessful response found when accessing the API")
            }
            return
        }
        
        guard let data = data, let result = try? JSONSerialization.jsonObject(with: data, options: []) else {
            DispatchQueue.main.async {
                self.showResultText("Couldn't deserialize result JSON")
            }
            return
        }
        
        DispatchQueue.main.async {
            self.showResultText("""
                            Accessed API successfully using access token.
                            HTTP response code: \(httpResponse.statusCode)
                            HTTP response body: \(result)
                            """)
        }
    }
    
    task.resume()
}

La méthode accessProtectedAPI envoie une requête GET au point de terminaison d’API spécifié en utilisant le jeton d’accès fourni. Elle configure la requête avec le jeton dans l’en-tête Authorization. Lorsqu’elle reçoit une réponse de réussite (code d’état HTTP 200-299), elle désérialise les données JSON et met à jour l’interface utilisateur avec le code d’état HTTP et le corps de la réponse. Si une erreur se produit pendant la gestion de la requête ou de la réponse, elle affiche le message d’erreur dans l’interface utilisateur. Cette méthode permet d’accéder à l’API 1 ou à l’API 2, en fonction de l’URL et du jeton d’accès fournis.