You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS Swift实现Azure AD B2C原生Sign in with Apple(无需网页)可行性咨询

iOS Swift中实现Azure AD B2C原生Sign in with Apple(无WebView)

是的,完全可以实现通过Apple原生系统授权弹窗完成Azure AD B2C登录,无需依赖WebView跳转。核心思路是:先通过Apple的AuthenticationServices框架获取原生授权码,再将该授权码传递给Azure AD B2C的令牌端点,直接换取身份令牌和访问令牌。

实现步骤

1. 集成Apple原生登录,获取授权码

使用Apple官方的AuthenticationServices框架唤起原生授权弹窗,同时遵循PKCE流程生成验证参数,确保请求安全性。

import AuthenticationServices

class AppleB2CLoginHandler: NSObject, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
    private var codeVerifier: String?
    
    // 启动Apple原生登录流程
    func initiateAppleLogin() {
        // 生成PKCE code verifier和challenge
        codeVerifier = generatePKCECodeVerifier()
        guard let codeChallenge = generatePKCECodeChallenge(from: codeVerifier!) else { return }
        
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let authRequest = appleIDProvider.createRequest()
        authRequest.requestedScopes = [.fullName, .email]
        // 传递PKCE challenge给Apple
        authRequest.nonce = codeChallenge
        
        let authController = ASAuthorizationController(authorizationRequests: [authRequest])
        authController.delegate = self
        authController.presentationContextProvider = self
        authController.performRequests()
    }
    
    // MARK: - ASAuthorizationControllerDelegate
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        guard let appleCredential = authorization.credential as? ASAuthorizationAppleIDCredential,
              let authCodeData = appleCredential.authorizationCode,
              let codeVerifier = codeVerifier else { return }
        
        let authCode = String(data: authCodeData, encoding: .utf8)!
        // 将Apple授权码传给Azure AD B2C换取令牌
        exchangeAppleCodeForB2CTokens(authCode: authCode, codeVerifier: codeVerifier)
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // 处理登录失败逻辑
        print("Apple登录失败: \(error.localizedDescription)")
    }
    
    // MARK: - ASAuthorizationControllerPresentationContextProviding
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        // 适配iOS 13+的窗口获取逻辑
        return UIApplication.shared.connectedScenes
            .filter({$0.activationState == .foregroundActive})
            .map({$0 as? UIWindowScene})
            .compactMap({$0})
            .first?.windows.first ?? UIWindow()
    }
    
    // MARK: - PKCE工具方法
    private func generatePKCECodeVerifier() -> String {
        let allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"
        var verifier = ""
        for _ in 0..<128 {
            let randomIndex = Int(arc4random_uniform(UInt32(allowedChars.count)))
            verifier.append(allowedChars[allowedChars.index(allowedChars.startIndex, offsetBy: randomIndex)])
        }
        return verifier
    }
    
    private func generatePKCECodeChallenge(from verifier: String) -> String? {
        guard let verifierData = verifier.data(using: .utf8) else { return nil }
        var sha256Buffer = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        verifierData.withUnsafeBytes {
            _ = CC_SHA256($0.baseAddress, CC_LONG(verifierData.count), &sha256Buffer)
        }
        let hashData = Data(sha256Buffer)
        return hashData.base64EncodedString()
            .replacingOccurrences(of: "+", with: "-")
            .replacingOccurrences(of: "/", with: "_")
            .replacingOccurrences(of: "=", with: "")
    }
}

2. 将Apple授权码传给Azure AD B2C换取令牌

构造POST请求调用Azure AD B2C的令牌端点,传入授权码、PKCE验证参数等信息,获取身份令牌(id_token)和访问令牌(access_token)。

private func exchangeAppleCodeForB2CTokens(authCode: String, codeVerifier: String) {
    let tenantName = "你的B2C租户名称"
    let signInPolicy = "你的登录策略名称"
    let clientId = "你的B2C应用客户端ID"
    let redirectUri = "你的注册重定向URI" // 需与Azure B2C应用配置一致,如msal{clientId}://auth
    let tokenEndpoint = "https://\(tenantName).b2clogin.com/\(tenantName).onmicrosoft.com/\(signInPolicy)/oauth2/v2.0/token"
    
    var request = URLRequest(url: URL(string: tokenEndpoint)!)
    request.httpMethod = "POST"
    request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    
    let parameters = [
        "grant_type": "authorization_code",
        "client_id": clientId,
        "code": authCode,
        "redirect_uri": redirectUri,
        "scope": "openid offline_access", // 按需添加其他权限范围
        "code_verifier": codeVerifier,
        "provider": "apple"
    ]
    
    // 编码请求参数
    let parameterString = parameters.map { key, value in
        "\(key)=\(value.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!)"
    }.joined(separator: "&")
    request.httpBody = parameterString.data(using: .utf8)
    
    URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, error == nil else {
            print("令牌交换失败: \(error?.localizedDescription ?? "未知错误")")
            return
        }
        
        do {
            if let tokenResponse = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
                if let idToken = tokenResponse["id_token"] as? String,
                   let accessToken = tokenResponse["access_token"] as? String {
                    // 处理令牌:存储到Keychain、更新用户登录状态等
                    DispatchQueue.main.async {
                        // 主线程更新UI或通知登录成功
                    }
                }
            }
        } catch {
            print("令牌响应解析失败: \(error.localizedDescription)")
        }
    }.resume()
}

关键注意事项

  • Azure B2C配置验证: 确保已在Azure门户中启用Sign in with Apple作为身份提供者,配置正确的Apple应用ID、Team ID,并上传Apple私钥;同时注册的重定向URI需与代码中一致。
  • Info.plist配置: 添加Apple登录所需的URL Scheme和权限声明:
    <key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleURLSchemes</key>
            <array>
                <string>msal你的客户端ID</string>
            </array>
        </dict>
    </array>
    <key>NSUserActivityTypes</key>
    <array>
        <string>com.apple.developer.applesignin</string>
    </array>
    
  • 持久化登录状态: 可通过ASAuthorizationAppleIDProvider().getCredentialState(forUserID:)方法检查用户登录状态,实现静默登录。

内容的提问来源于stack exchange,提问作者Radek Novak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 11:09:55