如何在不存储AppClientSecret的情况下实现AWS Cognito自定义登录?
Great question! Storing an App Client Secret in a mobile app is a critical security risk—reverse engineering tools can easily extract it, putting your user pool at risk. Let's walk through the correct approach to implement custom login without exposing this secret.
1. Use a Secret-Free App Client (Mandatory First Step)
AWS Cognito explicitly recommends creating an App Client without a secret for mobile apps. Here's how to set this up:
- Go to your Cognito User Pool in the AWS Console.
- Navigate to App integration > App clients and analytics.
- Create a new App Client, and make sure the Generate client secret checkbox is unchecked.
- Use this new App Client's ID in your code—you won't need a secret at all.
Update your initialization code to remove the client secret:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { let serviceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: nil) // Remove the clientSecret parameter or set it to nil let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration( clientId: CognitoIdentityUserPoolAppClientId, clientSecret: nil, poolId: CognitoIdentityUserPoolIdCognito ) AWSCognitoIdentityUserPool.register( with: serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey ) pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey) let credentialsProvider = AWSCognitoCredentialsProvider( regionType: .USEast1, identityPoolId: CognitoIdentityUserFederatedId, identityProviderManager: pool ) let configuration = AWSServiceConfiguration( region:.USEast1, credentialsProvider: credentialsProvider ) AWSServiceManager.default().defaultServiceConfiguration = configuration pool?.delegate = self return true }
2. Implement Custom Login with Cognito's Custom Authentication Flow
If you need custom login logic (e.g., validating against your own backend, custom MFA, or non-standard credentials), use Cognito's Custom Authentication Flow powered by Lambda triggers. This keeps all sensitive validation logic on the backend, so your app never handles secrets.
Key Lambda Triggers to Configure
You'll need to set up these three Lambda functions in your Cognito User Pool (under Integrations > Lambda triggers):
- Define Auth Challenge: Determines what challenge the user needs to complete (e.g., password verification, SMS code).
- Create Auth Challenge: Generates the challenge (e.g., sends an SMS code to the user's phone).
- Verify Auth Challenge Response: Checks if the user's response to the challenge is valid.
Example Custom Login Code
Here's how to initiate the custom auth flow from your iOS app:
func customLogin(username: String, password: String) { let authParams: [String: String] = [ "USERNAME": username, "PASSWORD": password // Or any custom parameters your Lambda expects ] let authRequest = AWSCognitoIdentityInitiateAuthRequest() authRequest?.clientId = CognitoIdentityUserPoolAppClientId authRequest?.authFlow = .customAuth authRequest?.authParameters = authParams pool?.initiateAuth(authRequest!).continueWith { task in DispatchQueue.main.async { if let error = task.error { print("Login failed: \(error.localizedDescription)") // Handle error (e.g., show alert to user) return } guard let result = task.result else { print("No auth result returned") return } if let authResult = result.authenticationResult { // Login successful! Use the tokens print("Access Token: \(authResult.accessToken!)") print("ID Token: \(authResult.idToken!)") // Proceed to navigate to your app's main screen } else if let challengeName = result.challengeName { // Handle the challenge (e.g., prompt user for SMS code) switch challengeName { case .smsMfa: // Prompt user to enter SMS code, then respond to the challenge self.respondToSMSChallenge(session: result.session!, username: username) case .passwordVerifier: // Handle password verification challenge (if needed) break default: print("Unknown challenge: \(challengeName.rawValue)") } } } return nil } } // Example: Respond to SMS MFA challenge func respondToSMSChallenge(session: String, username: String, smsCode: String) { let challengeResponse: [String: String] = [ "USERNAME": username, "SMS_MFA_CODE": smsCode ] let responseRequest = AWSCognitoIdentityRespondToAuthChallengeRequest() responseRequest?.clientId = CognitoIdentityUserPoolAppClientId responseRequest?.challengeName = .smsMfa responseRequest?.session = session responseRequest?.challengeResponses = challengeResponse pool?.respondToAuthChallenge(responseRequest!).continueWith { task in DispatchQueue.main.async { if let error = task.error { print("Challenge response failed: \(error.localizedDescription)") return } guard let result = task.result else { print("No challenge result returned") return } if let authResult = result.authenticationResult { // Challenge passed, login successful print("Access Token: \(authResult.accessToken!)") } } return nil } }
3. Critical Security Notes
- Never hardcode secrets: Even with a secret-free App Client, avoid hardcoding sensitive values like User Pool ID or Identity Pool ID if possible—use environment variables or secure configuration management.
- Validate Lambda inputs: Ensure your Lambda triggers validate all incoming parameters to prevent injection attacks.
- Use HTTPS exclusively: All communication between your app and Cognito/Lambda must use HTTPS to prevent man-in-the-middle attacks.
内容的提问来源于stack exchange,提问作者Rekd

