在iOS端不使用Cognito托管UI实现Amazon Cognito社交提供商关联的问题
在iOS端不使用Cognito托管UI实现Amazon Cognito社交提供商关联的问题
我完全懂你的困扰——用户已经用手机号好好登录了,结果要绑个社交账号还得先登出,这流程走起来用户肯定会懵。别发愁,咱们可以通过自定义流程绕开托管UI的限制,直接把社交账号和已登录的Cognito账号关联上,下面是具体的实现思路和代码示例:
核心思路
咱们不依赖Amplify的signInWithWebUI(),而是分三步走:
- 用原生社交SDK(比如GoogleSignIn、Facebook Login、Sign in with Apple)获取社交平台的认证Token
- 拿到当前已登录Cognito用户的唯一标识(sub)和会话信息
- 通过后端Lambda函数调用Cognito的管理员API,把社交账号和现有Cognito账号关联起来(因为前端不能直接调用需要管理员权限的API)
具体步骤
1. 集成原生社交SDK并获取认证Token
这一步直接用各平台的官方SDK,完全绕开Cognito的托管UI。举几个常用平台的例子:
Google 示例
import GoogleSignIn func fetchGoogleIdToken() async throws -> String { guard let rootVC = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .first?.windows .first?.rootViewController else { throw NSError(domain: "SocialAuth", code: -1, userInfo: [NSLocalizedDescriptionKey: "找不到可展示的视图控制器"]) } let signInResult = try await GIDSignIn.sharedInstance.signIn(withPresenting: rootVC) guard let idToken = signInResult.user.idToken?.tokenString else { throw NSError(domain: "SocialAuth", code: -2, userInfo: [NSLocalizedDescriptionKey: "无法获取Google ID Token"]) } return idToken }
Apple 示例
import AuthenticationServices func fetchAppleIdToken() async throws -> String { let provider = ASAuthorizationAppleIDProvider() let request = provider.createRequest() request.requestedScopes = [.fullName, .email] let authorizationController = ASAuthorizationController(authorizationRequests: [request]) let result = try await authorizationController.performAuthorization() guard let appleIDCredential = result.credential as? ASAuthorizationAppleIDCredential, let idTokenData = appleIDCredential.identityToken, let idToken = String(data: idTokenData, encoding: .utf8) else { throw NSError(domain: "SocialAuth", code: -3, userInfo: [NSLocalizedDescriptionKey: "无法获取Apple ID Token"]) } return idToken }
2. 获取当前已登录Cognito用户的信息
用户已经用手机号登录了,咱们直接从Amplify获取当前会话的用户ID(sub):
import Amplify func getCurrentCognitoUserSub() async throws -> String { let session = try await Amplify.Auth.fetchAuthSession() guard let cognitoSession = session as? AuthCognitoSession, let userSub = cognitoSession.userSubResult.value else { throw NSError(domain: "CognitoAuth", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取当前登录用户的ID"]) } return userSub }
3. 通过后端Lambda关联账号
因为Cognito的AdminLinkProviderForUserAPI需要管理员权限,不能在前端直接调用,所以咱们得写个Lambda函数来做这件事:
Lambda函数核心代码(Python示例)
import boto3 import jwt cognito_client = boto3.client('cognito-idp') def lambda_handler(event, context): user_pool_id = "你的Cognito用户池ID" target_user_sub = event['user_sub'] # 前端传过来的当前Cognito用户sub provider_name = event['provider_name'] # 比如"Google"、"Facebook"、"SignInWithApple" provider_token = event['provider_token'] # 前端传过来的社交Token # 解析社交Token获取用户唯一ID,不同提供商解析方式不同 if provider_name == "Google": decoded_token = jwt.decode(provider_token, options={"verify_signature": False}) provider_user_id = decoded_token['sub'] elif provider_name == "Facebook": # Facebook需要用access token调用其API获取用户ID,或者直接用token解析 decoded_token = jwt.decode(provider_token, options={"verify_signature": False}) provider_user_id = decoded_token['id'] elif provider_name == "SignInWithApple": decoded_token = jwt.decode(provider_token, options={"verify_signature": False}) provider_user_id = decoded_token['sub'] else: raise ValueError("不支持的提供商") response = cognito_client.admin_link_provider_for_user( UserPoolId=user_pool_id, DestinationUser={ 'ProviderName': 'Cognito', 'ProviderAttributeName': 'sub', 'ProviderAttributeValue': target_user_sub }, SourceUser={ 'ProviderName': provider_name, 'ProviderAttributeName': 'Cognito_Subject', 'ProviderAttributeValue': provider_user_id } ) return {'statusCode': 200, 'body': '账号关联成功'}
然后前端通过API网关调用这个Lambda函数,把需要的参数传过去就行:
func linkSocialAccountToCurrentUser(providerName: String, providerToken: String) async throws { let userSub = try await getCurrentCognitoUserSub() let parameters: [String: Any] = [ "user_sub": userSub, "provider_name": providerName, "provider_token": providerToken ] // 这里调用你的API网关端点,发送POST请求 let url = URL(string: "你的API网关URL")! var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = try JSONSerialization.data(withJSONObject: parameters) request.setValue("application/json", forHTTPHeaderField: "Content-Type") let (_, response) = try await URLSession.shared.data(for: request) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw NSError(domain: "LinkAccountError", code: -1, userInfo: [NSLocalizedDescriptionKey: "账号关联失败"]) } }
注意事项
- 要确保你的Cognito用户池已经开启了对应的社交提供商的登录选项
- 解析社交Token获取用户ID的时候,要注意不同提供商的字段差异:Google是
sub,Facebook是id,Apple是sub - Lambda函数要配置足够的权限(需包含Cognito的
cognito-idp:AdminLinkProviderForUser权限)
内容来源于stack exchange
相关产品推荐
相关产品推荐

