Que fait authentification ?
Implémentez des flux d'authentification iOS avec AuthenticationServices et LocalAuthentication. Utilisez-le pour créer une connexion avec Apple, l'enregistrement ou la connexion par passkey/WebAuthn avec ASAuthorizationPlatformPublicKeyCredentialProvider, la gestion de l'état des credentials et de la révocation avec ASAuthorizationController, les connexions OAuth ou tierces avec ASWebAuthenticationSession, le remplissage automatique de mots de passe, la validation côté serveur des jetons d'identité, ou la ré-authentification biométrique locale avec LAContext.
Exigences
- iOS 15.0+ (requis pour ASAuthorizationPlatformPublicKeyCredentialProvider)
- Xcode 14+
- Entitlements :
com.apple.developer.authentication-services.autorevoke(pour la gestion de la révocation) - Entitlements :
com.apple.developer.applesignin(pour la connexion avec Apple) - Info.plist :
NSFaceIDUsageDescription(pour Face ID)
Utilisation et exemples de code
1. Connexion avec Apple
import AuthenticationServices
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let controller = ASAuthorizationController(authorizationRequests: [request])
controller.delegate = self
controller.presentationContextProvider = self
controller.performRequests()
// Gérer les résultats dans le délégué :
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
let identityToken = appleIDCredential.identityToken
let authorizationCode = appleIDCredential.authorizationCode
// Envoyer identityToken et authorizationCode à votre serveur pour validation
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Gérer l'erreur
}
2. Passkey / WebAuthn
// Créer un fournisseur pour les passkeys
let platformProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: "example.com")
// Enregistrement
let registrationRequest = platformProvider.createCredentialRegistrationRequest(challenge: challengeData, name: "[email protected]", userID: userIDData)
// Connexion
let assertionRequest = platformProvider.createCredentialAssertionRequest(challenge: challengeData)
let controller = ASAuthorizationController(authorizationRequests: [registrationRequest])
controller.delegate = self
controller.performRequests()
// Vérifier les résultats :
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if #available(iOS 15.0, *) {
if let credentialRegistration = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialRegistration {
// Stocker credentialID et le credential côté serveur
} else if let credentialAssertion = authorization.credential as? ASAuthorizationPlatformPublicKeyCredentialAssertion {
// Vérifier la signature avec credentialID et le credential côté serveur
}
}
}
3. Gestion de l'état des credentials et de la révocation
import AuthenticationServices
// Surveiller les changements d'état des credentials
let appleIDProvider = ASAuthorizationAppleIDProvider()
// Vérifier l'état d'un identifiant utilisateur (par exemple, au lancement de l'application)
appleIDProvider.getCredentialState(forUserID: "votre-identifiant-utilisateur-stocké") { (credentialState, error) in
switch credentialState {
case .authorized:
// L'identifiant utilisateur est toujours valide
case .revoked:
// L'utilisateur s'est déconnecté de l'application
// Supprimer les données utilisateur locales et rediriger vers l'écran de connexion
case .notFound:
// L'identifiant utilisateur n'existe pas
// Dans ce cas, traiter comme une nouvelle inscription
default:
break
}
}
// S'abonner aux notifications de révocation (nécessite l'entitlement com.apple.developer.authentication-services.autorevoke)
// L'application recevra des notifications lorsque les credentials sont révoqués de manière asynchrone
4. Connexion OAuth / tierce avec ASWebAuthenticationSession
import AuthenticationServices
class AuthManager: NSObject, ASWebAuthenticationPresentationContextProviding {
var webAuthSession: ASWebAuthenticationSession?
func startOAuthFlow() {
guard let authURL = URL(string: "https://example.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=yourapp://callback&response_type=code&scope=openid%20profile") else { return }
let callbackURLScheme = "yourapp"
webAuthSession = ASWebAuthenticationSession(url: authURL, callbackURLScheme: callbackURLScheme) { callbackURL, error in
guard error == nil, let callbackURL = callbackURL else {
// Gérer l'erreur
return
}
// Extraire le code d'autorisation de l'URL de callback
// URL typique : yourapp://callback?code=AUTHORIZATION_CODE&state=STATE
let queryItems = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false)?.queryItems
let authorizationCode = queryItems?.filter({ $0.name == "code" }).first?.value
// Échanger le code d'autorisation contre des jetons via une requête réseau
}
webAuthSession?.presentationContextProvider = self
webAuthSession?.prefersEphemeralWebBrowserSession = true // Utiliser un navigateur privé
webAuthSession?.start()
}
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return UIApplication.shared.windows.first ?? ASPresentationAnchor()
}
}
5. Remplissage automatique de mots de passe
// Configurer les champs de texte dans votre vue pour le remplissage automatique :
let usernameTextField = UITextField()
usernameTextField.textContentType = .username
let passwordTextField = UITextField()
passwordTextField.textContentType = .password
// Pour la connexion, configurer le type de contenu du formulaire :
// Dans votre ViewController ou votre vue, associer les champs
// Le remplissage automatique iOS gérera l'affichage des suggestions de mots de passe
// Pour un nouveau compte :
let newPasswordTextField = UITextField()
newPasswordTextField.textContentType = .newPassword
// Activer le partage de mots de passe avec les credentials associés
6. Validation côté serveur des jetons d'identité
// Envoyer le jeton d'identité du côté client au serveur pour validation
// Le jeton provient d'ASAuthorizationAppleIDCredential.identityToken
func sendIdentityTokenToServer(identityToken: Data, authorizationCode: Data) {
// Convertir les données en chaîne base64
let identityTokenString = identityToken.base64EncodedString()
let authorizationCodeString = authorizationCode.base64EncodedString()
let parameters: [String: Any] = [
"identity_token": identityTokenString,
"authorization_code": authorizationCodeString,
"user_id": "votre-identifiant-utilisateur-stocké"
]
// Effectuer une requête POST vers votre serveur
// Le serveur doit valider le jeton d'identité JWT en utilisant la clé publique d'Apple
// et échanger le code d'autorisation contre des jetons de rafraîchissement si nécessaire
}
7. Ré-authentification biométrique locale avec LAContext
import LocalAuthentication
func authenticateWithBiometrics() {
let context = LAContext()
var error: NSError?
// Configurer la politique d'évaluation
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
if let error = error {
// Gérer l'erreur (par exemple, Face ID non disponible)
return
}
let reason = "Authentifiez-vous pour accéder à vos données sécurisées"
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, evaluateError in
DispatchQueue.main.async {
if success {
// Authentification réussie, procéder à l'action sécurisée
} else {
// Gérer l'erreur d'authentification (par exemple, tentative annulée)
if let laError = evaluateError as? LAError {
switch laError.code {
case .userCancel:
// L'utilisateur a annulé
case .authenticationFailed:
// Échec de l'authentification
case .userFallback:
// L'utilisateur a choisi d'utiliser un mot de passe (fallback)
default:
break
}
}
}
}
}
// Pour une politique plus flexible (biométrie OU code d'accès) :
// context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in ... }
}
// Configurer les options de contrôle d'accès pour un élément de trousseau :
let accessControl = SecAccessControlCreateWithFlags(nil,
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
.userPresence, // Nécessite la présence de l'utilisateur (biométrie ou code d'accès)
nil)
// Utiliser ce contrôle d'accès lors de l'ajout d'éléments au trousseau
Meilleures pratiques
- Mécanismes de secours : Implémentez toujours des mécanismes de secours pour les cas d'absence de biométrie ou d'échec.
- File d'attente principale : effectuez les mises à jour de l'interface utilisateur dans les rappels d'évaluation sur la file d'attente principale.
- Gestion des erreurs : gérez les erreurs
LAErrorpour chaque cas d'échec (annulation par l'utilisateur, échec d'authentification, secours, etc.). - Jetons d'identité : validez toujours les jetons d'identité côté serveur ; ne faites jamais confiance aux jetons reçus du client sans vérification.
- Révocation : gérez correctement la révocation des credentials en supprimant les données utilisateur locales et en déconnectant l'utilisateur.
- Passkeys : utilisez
ASAuthorizationPlatformPublicKeyCredentialProvideruniquement pour iOS 15.0+. Assurez-vous que votre serveur prend en charge WebAuthn. - ASWebAuthenticationSession : préférez
prefersEphemeralWebBrowserSession = truepour éviter de partager des cookies avec Safari, sauf si l'application a besoin de les conserver.