Closed helloniklas closed 7 years ago
@mnolanjr98 Any solution to connecting your User Pool with a unique Identity Pool? I'm having the same issue.
Regarding connecting your User Pool with a unique Identity Pool, looks like after a successful getSession() call, you are supposed ti just call getIdentityId() or getIdentityId().continueWithBlock. It's not clear for a User Pool auth if the logins need to be provided. The current CredentialsProvider.logins call is deprecated. If I implement the custom AWSIdentityProviderManager, I'm not clear where to get the logins value. Is there a complete example anyone has come across that shows how to connect a User Pool with Identity Pool, so that each user has a unique Identity Pool?
@bpeck81 Re:User Pool/Identity Pool on Aug 6, I've been stuck on getting a unique Identity Pool for each new user for a while. My AppDelegate looks exactly like yours, but after signing up a new user, the logging them in with the pool.getUser(), call user.getsession(), and credentialsprovider.getIdentityId() sequence, each user is still using the same identity pool.
Any suggestions?
As a note, here's one way to put it together. This is using vincent-coetzee's class as well as some StackOverflow stuff. We're using amazon cognito for auth in our user pool.
The key was finally understanding the relationship between User Pools and the Federated Identities. The FI is really a bridge to amazon credentials; a FI doesn't do authentication, it basically provides authorization to AWS resources. You authenticate against a user pool because that's the part that understands authentication. The IdentityProvider allows the FI to map an amazon authenticated identity (those two IAM users) to a User Pool's authenticated identity.
Without further ado, here's the good stuff:
// from vincent-coetzee
// AWSCustomIdentityProvider.swift
// be sure to change your CognitoTokenKey
// YOUR_USER_POOL_ID should look like this -> us-west-5_bl0yh29v5
// your COGNITO_REGION should be like 'us-west-2'
import Foundation
import AWSCore
import AWSCognitoIdentityProvider
import AWSCognito
class AWSCustomIdentityProvider: NSObject, AWSIdentityProviderManager
{
static let FacebookTokenKey = "graph.facebook.com"
static let GoogleTokenKey = "accounts.google.com"
static let TwitterTokenKey = "api.twitter.com"
static let CognitoTokenKey = "cognito-idp.COGNITO_REGION.amazonaws.com/YOUR_USER_POOLID"
private var tokens : [NSString : NSString] = [:]
func addToken(key:String,value:String)
{
tokens[key] = value
}
@objc func logins() -> AWSTask
{
return AWSTask(result: tokens)
}
}
In your AppDelegate.swift. Note you don't have to do this here...and all you're doing is setting up your user pool.
let serviceConfiguration = AWSServiceConfiguration(region: Constants.COGNITO_REGIONTYPE, credentialsProvider: nil)
let configurationUserPool = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.COGNITO_USER_POOL_APP_CLIENT_ID, clientSecret: Constants.COGNITO_USER_POOL_APP_CLIENT_SECRET, poolId: Constants.COGNITO_USER_POOL_ID)
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: configurationUserPool, forKey: Constants.USER_POOL_KEY)
self.pool = AWSCognitoIdentityUserPool(forKey: Constants.USER_POOL_KEY)
To log in we ask the pool for a user with a specific username/email address/alias then getSession is what authenticates the user
//LOGIN
func login(email:String, password:String, callback:((error:String?)->Void)) {
let user = self.pool!.getUser(theUsernameOrAlias!)
user.getSession(theUsernameOrAlias!, password: password, validationData: nil).continueWithBlock({ task in
if let err = task.error { // some sort of error
print("LOGIN FAILED")
print("Domain: \(err.domain)! Code: \(err.code)")
//print(err.userInfo["message"] as! String)
if err.code == 23 { //Not sure here, exists doesn't mean CONFIRMED or UNCONFIRMED, maybe login?
callback(error:"UNCONFIRMED")
}
else {
callback(error:"ERROR")
}
}
else { //Successful login!
// this gets our token from the User Pool
let ret = task.result as! AWSCognitoIdentityUserSession
let myToken = ret.idToken?.tokenString;
print("Token: ", myToken);
// this sets up our custom provider. It allows cognito FI to map an identity to a user pool token.
// note that I believe the user pool token must be consistent.
let myProvider = AWSCustomIdentityProvider();
// let's add that token tot he provider
myProvider.addToken(AWSCustomIdentityProvider.CognitoTokenKey, value: myToken!)
// now we set that custom provider up as a reference for the Cognito FI
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USWest2, identityPoolId: Constants.COGNOTO_IDENTITY_POOL_ID, identityProviderManager:myProvider)
// add that FI credentials provider to the AWS config
let serviceConfiguration = AWSServiceConfiguration(region: Constants.COGNITO_REGIONTYPE, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = serviceConfiguration;
// wipe cached creds
credentialsProvider.clearKeychain()
credentialsProvider.clearCredentials()
// hit it
credentialsProvider.getIdentityId().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("Error: ")
} else {
// the task result will contain the identity id
print("Success with id")
print(task.result)
print("After cp.logins call")
}
callback(error:nil)
return nil
}
}
return nil
})
}
This is with xcode 7 and swift 2.something or another. Hope this helps someone, this was sort of a nightmare to hunt down.
Here is some documentation for using DevAuth in Objective-C. It covers using DevAuth alone, DevAuth with unauthenticated and DevAuth with social providers. If you have feedback on the content or something isn't clear, please provide that feedback. This will be the basis for updating the documentation.
To use developer authenticated identities, implement your own identity provider class which extends AWSCognitoCredentialsProviderHelper.
@implementation DeveloperAuthenticatedIdentityProvider
/*
* Use the token method to communicate with your backend to get an
* identityId and token.
*/
- (AWSTask <NSString*>) token {
//Write code to call your backend:
//pass username/password to backend or some sort of refresh token to authenticate user, if successful,
//from backend call getOpenIdTokenForDeveloperIdentity with logins map containing "your.provider.name":"enduser.username"
//return the identity id and token to client
//You can use AWSTaskCompletionSource to do this asynchronously
// Set the identity id and return the token
self.identityId = response.identityId;
return [AWSTask taskWithResult:response.token];
}
@end
To use this identity provider, pass it into AWSCognitoCredentialsProvider as shown in the following example:
DeveloperAuthenticatedIdentityProvider * devAuth = [[DeveloperAuthenticatedIdentityProvider alloc] initWithRegionType:AWSRegionYOUR_IDENTITY_POOL_REGION
identityPoolId:@"YOUR_IDENTITY_POOL_ID"
useEnhancedFlow:YES
identityProviderManager:nil];
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc]
initWithRegionType:AWSRegionYOUR_IDENTITY_POOL_REGION
identityProvider:devAuth];
If you want to support both unauthenticated identities and developer authenticated identities, override the logins method in your AWSCognitoCredentialsProviderHelper implementation.
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
if(/*logic to determine if user is unauthenticated*/) {
return [AWSTask taskWithResult:nil];
}else{
return [super logins];
}
}
If you want to support developer authenticated identities and social providers, you must manage who the current provider is in your logins implementation of AWSCognitoCredentialsProviderHelper.
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
if(/*logic to determine if user is unauthenticated*/) {
return [AWSTask taskWithResult:nil];
}else if (/*logic to determine if user is Facebook*/){
return [AWSTask taskWithResult: @{ AWSIdentityProviderFacebook : [FBSDKAccessToken currentAccessToken] }];
}else {
return [super logins];
}
}
@behrooziAWS Thanks for this write up, the documentation is seriously lacking some info about this.
Was able to get this working using the logins method (the docs , again, are unclear about when to use token vs when to use logins, but it seems they dont work together)
refresh()
protocol method anymore? You don't return those to the client at all, instead, upon completion of authentication with your backend, you call GetOpenIdTokenForDeveloperIdentity (from your backend) and return the token and identity id to the client. See details of what to do in the token method in the documentation above.
@behrooziAWS The docs you provided aren't clear on the correlation between logins
and token
. Also, if we're just providing token, we can't provide a custom providerName
as a key like we would in the logins
map. Should the logins method use the token from the token() method ?
A lot of confusion around this.
You don't need to override the logins method on ios, there already is an implementation for you if you extend AWSCognitoCredentialsProviderHelper. You only need to override it if you want to combine unauthenticated users or social login providers with your dev auth provider. The default implementation of logins sets the provider name to cognito-identity.amazonaws.com which is what it should be for dev auth. The place you need your custom provider name is when you call GetOpenIdTokenForDeveloperIdentity from your backend, not in ios.
And here is the swift documentation:
To use developer authenticated identities, implement your own identity provider class which extends AWSCognitoCredentialsProviderHelper.
import AWSCore
/*
* Use the token method to communicate with your backend to get an
* identityId and token.
*/
class DeveloperAuthenticatedIdentityProvider : AWSCognitoCredentialsProviderHelper {
override func token() -> AWSTask<NSString> {
//Write code to call your backend:
//pass username/password to backend or some sort of refresh token to authenticate user, if successful,
//from backend call getOpenIdTokenForDeveloperIdentity with logins map containing "your.provider.name":"enduser.username"
//return the identity id and token to client
//You can use AWSTaskCompletionSource to do this asynchronously
// Set the identity id and return the token
self.identityId = resultFromAbove.identityId
return AWSTask(result: resultFromAbove.token)
}
To use this identity provider, pass it into AWSCognitoCredentialsProvider as shown in the following example:
let devAuth = DeveloperAuthenticatedIdentityProvider(regionType: .YOUR_IDENTITY_POOL_REGION, identityPoolId: "YOUR_IDENTITY_POOL_ID", useEnhancedFlow: true, identityProviderManager:nil)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .YOUR_IDENTITY_POOL_REGION, identityProvider:devAuth)
let configuration = AWSServiceConfiguration(region: .YOUR_IDENTITY_POOL_REGION, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
If you want to support both unauthenticated identities and developer authenticated identities, override the logins method in your AWSCognitoCredentialsProviderHelper implementation.
override func logins () -> AWSTask<NSDictionary> {
if(/*logic to determine if user is unauthenticated*/) {
return AWSTask(result:nil)
}else {
return super.logins()
}
}
If you want to support developer authenticated identities and social providers, you must manage who the current provider is in your logins implementation of AWSCognitoCredentialsProviderHelper.
override func logins () -> AWSTask<NSDictionary> {
if(/*logic to determine if user is unauthenticated*/) {
return AWSTask(result:nil)
}else if (/*logic to determine if user is Facebook*/){
if let token = AccessToken.current?.authenticationToken {
return AWSTask(result: [AWSIdentityProviderFacebook:token])
}
return AWSTask(error:NSError(domain: "Facebook Login", code: -1 , userInfo: ["Facebook" : "No current Facebook access token"]))
}else {
return super.logins()
}
}
@behrooziAWS Wouldn't it be better to add this to your official docs, they are very outdated, and downloading samples from mobile hub is even worst.
I've passed this content to the docs team. When they incorporate it, I will resolve this issue.
@behrooziAWS Can I please trouble you for a sample on how to use Digits with Cognito? I am encountering the same problem with trying to use Digits with the new SDK. I followed the existing examples for 2.36 and basically stuck on this step to set the logins :(
Please help.
@georgel likely you will need to implement an IdentityProviderManager that does something like this:
@implementation DigitsIdentityProviderManager
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
AWSTaskCompletionSource<NSDictionary<NSString *, NSString *> *> * completion = [AWSTaskCompletionSource new];
[[Digits sharedInstance] authenticateWithCompletion:^
(DGTSession* session, NSError *error) {
if (!error) {
NSString value = [NSString stringWithFormat:@"%@;%@", session.authToken, session.authTokenSecret];
completion.result = @{@"www.digits.com", value};
} else {
completion.error = error;
}
}];
return completion.task;
}
@end
@behrooziAWS Thank you, David and all the helpful folks on this thread. I finally got it to at least login with Digit.
Can someone please give me a tip on how to go from un-auth user to an auth-ed user. When a user first open the app, they are assigned a un-auth identityId. After they login in, I am able to get the correct authed identityId. But when the app relaunches, the credentialProvider.identityId is still the un-auth id.
Here's what I piece together from all the helpful tips above. Please let me know if I am missing a step somewhere :)
An IdentityProviderManager:
class DigitsIdentityProviderManager:NSObject, AWSIdentityProviderManager {
public func logins() -> AWSTask<NSDictionary> {
let completion = AWSTaskCompletionSource<NSDictionary>()
if let configuration = DGTAuthenticationConfiguration(accountFields: .defaultOptionMask) {
configuration.appearance = DGTAppearance()
configuration.appearance.backgroundColor = UIColor.white
configuration.appearance.accentColor = UIColor.tintColor()
Digits.sharedInstance().authenticate(with: nil, configuration:configuration) {(session, error) in
if session != nil {
let value = session!.authToken + ";" + session!.authTokenSecret
completion.setResult(["www.digits.com" : value as NSString])
} else {
completion.setError(error!)
}
}
}
return completion.task
}
}
Invoked by
func handleDigitLogin() {
let digitsIdentityProviderManager = DigitsIdentityProviderManager()
let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.usEast1,
identityPoolId:Constants.Aws.CognitoPoolId,
identityProviderManager:digitsIdentityProviderManager)
let serviceConfiguration = AWSServiceConfiguration(region: .usEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
let task = credentialsProvider.getIdentityId()
task.continue(successBlock: { (task:AWSTask) -> Any? in
if (task.error != nil ) {
print("\(task.error)")
} else {
print("Task result: \(task.result)")
}
return nil
})
}
If I may make a request. I am guessing it's challenging to keep docs up to date with each release. It would be nice if the SDK sample contained tests for the supported public providers and a sample custom provider. It would be much easier to use a test as the reference for me.
@georgel Once an identity becomes authenticated, you cannot get credentials against it unless you provide a valid token. The recommended approach is to instantiate your credentials provider in your AppDelegate
because you can only set the defaultServiceConfiguration
once. Whenever you need to access it you use AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider
If you are switching end users, you should use credentialsProvider.clearKeychain()
this will blow away both your identity id and credentials. If you are switching between unauthenticated and authenticated you should use credentialsProvider.clearCredentials()
because this will preserve your identity id. In other circumstances you shouldn't clear anything, i.e. if you relaunch the app and your credentials have expired the SDK will invoke your DigitsIdentityProviderManager.logins()
to get a current token. To handle unauth and auth together, you should just have your DigitsIdentityProviderManager
return a nil logins map when the user has never authenticated before otherwise call the Digits logic. If they sign out, you can start returning a nil logins map again. You should only call getIdentityId
if you really need to, simply making calls to AWS services will get an identity id and credentials against that identity on demand if they aren't already present or have expired.
Thank you for your patience! The documentation has been updated for using developer authenticated identities with iOS SDK 2.4.x. http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html We will work to update the documentation for other identity providers, in the meantime there are a few examples of how to integrate with Facebook and Twitter in this thread.
If I leave out the auth roles from the AWSCognitoCredentialsProvider
the request to upload to S3 times out:
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x171a45160 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=https://s3-eu-west-1.amazonaws.com/myserver-server/staging/avatar/1480977990.5395651.jpg, NSErrorFailingURLKey=https://s3-eu-west-1.amazonaws.com/myserver-server/staging/avatar/1480977990.5395651.jpg, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.}
If I include the roles:
Error Domain=com.amazonaws.AWSServiceErrorDomain Code=11 "(null)" UserInfo={Type=Sender, Message=Not authorized to perform sts:AssumeRoleWithWebIdentity, Code=AccessDenied, __text=(
"\n ",
"\n ",
"\n ",
"\n "
)}
Also in the developer authenticated identities Swift example, the identity provider is implemented using Objective-C which is of little help. It would be great to get the meat of that example in Swift.
I seemed to have solved my problem for my implementation of a Developer Authenticated only support. For those who were having similar troubles to me here is my setup. Open to getting this cleaned up if anyone has any suggestions. (Swift 3):
final class AmazonIdentityProvider: AWSCognitoCredentialsProviderHelper {
var cachedLogin: NSDictionary?
// Handles getting the login
override func logins() -> AWSTask<NSDictionary> {
guard let cachedLogin = cachedLogin else {
return getCredentials().continue({ credentialTask -> AWSTask<NSDictionary> in
guard let credential = credentialTask.result else {
return AWSTask(result: nil)
}
let login: NSDictionary = ["cognito-identity.amazonaws.com": credential.token]
self.cachedLogin = login
return AWSTask(result: login)
}) as! AWSTask<NSDictionary>
}
return AWSTask(result: cachedLogin)
}
// Handles getting a token from the server
override func token() -> AWSTask<NSString> {
return getCredentials().continue({ credentialTask -> AWSTask<NSString> in
guard let credential = credentialTask.result else {
return AWSTask(result: nil)
}
return AWSTask(result: credential.token as NSString)
}) as! AWSTask<NSString>
}
// Handles getting the identity id
override func getIdentityId() -> AWSTask<NSString> {
return getCredentials().continue({ credentialTask -> AWSTask<NSString> in
guard let credential = credentialTask.result else {
return AWSTask(result: nil)
}
return AWSTask(result: credential.identityId as NSString)
}) as! AWSTask<NSString>
}
// Gets credentials from server
func getCredentials() -> AWSTask<AmazonCognitoCredential> { // AmazonCognitoCredential is a class I created to store credentials from my server
let tokenRequest = AWSTaskCompletionSource<AmazonCognitoCredential>()
// Replace this with your code that calls your backend
Session.getAwsToken.makeRequest(failure: { error in
guard let error = error else { return }
tokenRequest.setError(error)
print("Could not fetch AWS credentials from server: \(error)")
}, completion: { credentials in
guard let credentials = credentials else { return }
tokenRequest.setResult(credentials)
})
return tokenRequest.task
}
}
/// Handles uploads to AWS S3
struct S3ImageUploader {
/// Singleton instance so that the credentials provider
/// can cache AWS credentials without having to hit
/// server constantly
static let main = S3ImageUploader()
private lazy var identityProvider: AmazonIdentityProvider = {
return AmazonIdentityProvider(regionType: .euWest1, identityPoolId: IdentityPoolId, useEnhancedFlow: true, identityProviderManager: nil)
}()
private lazy var credentialsProvider: AWSCognitoCredentialsProvider = {
return AWSCognitoCredentialsProvider(regionType: .euWest1, identityProvider: self.identityProvider)
}()
private lazy var serviceConfiguration: AWSServiceConfiguration = {
return AWSServiceConfiguration(region: .usWest2, credentialsProvider: self.credentialsProvider)
}()
/// Set the default service configuration
init() {
// Set configuration
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
}
fun doUpload() {
// Your implementation here
}
}
final class AmazonCognitoCredential {
let token: String
let identityId: String
init(token: String, identityId: String) {
self.token = token
self.identityId = identityId
}
}
First me let me get this out of the way:
The AWS SDK is huge, VERY HARD to follow (if you practically didn't write it), IS NOT very well documented and has many bugs (many which I have uncovered).
To the iOS AWS SDK Team:
With that being said, after finally deciding to unpin my AWS SDK from v2.3.6 and dedicating some time to getting the latest SDK version working heres what I found after spending nearly ~8+ hrs dissecting the AWS SDK - alot of which was harder to debug in XCode due to all the callbacks implemented in the code. Enjoy 🎉!
To provide federated identities with cognito at the most basic level what has to happen is that your backend has to make a call to GetOpenIdToken to get a Cognito token for your Cognito Identity Pool. This token is returned to your iOS app and it is this token that will be used to authenticate with AWS.
1. Create a Custom Identity Provider that SubClasses AWSCognitoCredentialsProviderHelper Any references to [WinkAPI sharedManager].session imply checking if your app has a valid user session. Replace with your own calls to validate your app session
//
// WinkIdentityProvider.h
// Wink
//
// Created by Adonis Peralta on 5/26/15.
// Copyright (c) 2015 TouchSix, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AWSCore/AWSIdentityProvider.h>
@interface WinkIdentityProvider : AWSCognitoCredentialsProviderHelper
@end
//
// WinkIdentityProvider.m
// Wink
//
// Created by Adonis Peralta on 5/26/15.
// Copyright (c) 2015 TouchSix, Inc. All rights reserved.
//
#import <AWSCore/AWSCore.h>
#import "WinkAPI.h"
#import "WinkIdentityProvider.h"
#import "Session.h"
#import "AWSToken.h"
#import "Person.h"
@interface WinkIdentityProvider ()
@property (nonatomic, copy) NSString *awsToken;
@end
@implementation WinkIdentityProvider
- (AWSTask *)token
{
return [[AWSTask taskWithResult:nil] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) {
return [self refresh];
}];
}
// call to backend, called through entry point
- (AWSTask *)refresh
{
if (![WinkAPI sharedManager].session) return [AWSTask taskWithError:[NSError errorWithDomain:kWinkErrorDomain code:1 userInfo:@{NSLocalizedDescriptionKey:@"unable to refresh AWS token, no valid WinkAPI session."}]];
AWSTaskCompletionSource *task = [AWSTaskCompletionSource taskCompletionSource];
[[WinkAPI sharedManager] refreshAWSTokenWithCompletion:^(AWSToken *token, NSError *error) {
if (error) {
[task setError:error];
return;
}
self.identityId = token.identityId;
self.awsToken = token.uid;
[task setResult:self.awsToken];
}];
return task.task;
}
// tells aws in its api call if your user is authenticated or not - this correlates with the authRoleArn and unAuthRoleArn parameters of your identity pool
- (BOOL)isAuthenticated
{
if ([WinkAPI sharedManager].session) return YES;
return NO;
}
// override of the clear method of the identity provider so you can clean up your custom identity provider
- (void)clear
{
[super clear];
self.awsToken = nil;
self.identityId = nil;
}
@end
2. Initialize and setup your AWS Credentials in a class implementation of your choosing
// AWSHandlers.m
#import "WinkIdentityProvider.h"
#import "AWSCore.h"
#import "AWSS3.h"
@interface AWSHandlers ()
@property (nonatomic, strong) AWSCognitoCredentialsProvider *awsCredentialsProvider;
@property (nonatomic, strong) AWSS3TransferManager *s3TransferManager;
@end
@implementation AWSHandlers
- (void)setupAWS
{
// instantiate our custom identity provider
WinkIdentityProvider *winkProvider = [[WinkIdentityProvider alloc] initWithRegionType:AWSRegionUSEast1
identityPoolId:@"identity-pool-id-here"
useEnhancedFlow:YES
identityProviderManager:nil];
// instantiate the credentials provider with our customer identity provider
self.awsCredentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
identityProvider:winkProvider];
AWSServiceConfiguration *defaultAWSConfig = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:self.awsCredentialsProvider];
[[AWSServiceManager defaultServiceManager] setDefaultServiceConfiguration:defaultAWSConfig];
// setup a s3 transfer manager
self.s3TransferManager = [AWSS3TransferManager defaultS3TransferManager];
// change the verbosity level of the aws sdk if you choose
//[AWSLogger defaultLogger].logLevel = AWSLogLevelVerbose;
}
// call on app logout AND login! If not, cached credentials which may or may not be valid can be used during an app reinstall, or first loading of your app (not resuming from background)
- (void)invalidateAWSCredentialsAndHandlers
{
[self cancelAllCurrentS3Operations];
[self.awsCredentialsProvider clearKeychain];
self.awsCredentialsProvider = nil;
self.s3TransferManager = nil;
}
1. Initialize your custom identity provider via initWithRegionType:identityPoolId. Do not use the other methods that specify a identity provider manager as an identity provider manager is for when not using developer provided identities.
2. If the token you provided is invalid or EXPIRED the AWS SDK will attempt to refresh your token (via getIdentityId method) ONE more time. After that IF IT FAILS again the call that triggered the getting of AWS credentials (for example downloading an AWS image) will fail.
3. The Custom Identity Provider (BOOL)isAuthenticated method is needed to tell AWS in its api call if your user is authenticated or not. This correlates with the authRoleArn and unAuthRoleArn.
4. Override the Custom Identity Provider -(void)clear method to do any clean up of your custom identity provider variables if necessary.
After initializing and seting up the aws cognito credentials as described above in step #3 here are some quirks to know of about the AWS SDK:
**If you do not clear the provider keychain on login, the AWS SDK will attempt to use the keychain stored credentials instead of calling your backend for a valid token again. Failing to do so has very bad implications:
Update: The above post has been updated to factor a misunderstanding about the IdentityProviderManager class. Initially I believed it was part of the developer identity provider flow but it is not. This simplifies many of the findings I had found previously in regards to the use of enhancedFlows vs non-enhanced throughout the authentication process.
Hello, To login with Developer Authenticated Identities in SDK AWS v2.4.16 I have a ILDeveloperAuthenticatedIdentityProvider (:AWSCognitoCredentialsProviderHelper ) where has implemented “(AWSTask<NSString > )token” among others like AWS Doc explain, but this “token” function never called. Why could this be happening?
Hi @ivanfervar,
Did you read my post above above. Its a bit long and detailed but will definitely explain what's happening behind the scenes and should help you out.
On Jan 11, 2017, at 7:33 AM, ivanfervar notifications@github.com wrote:
Hello, To login with Developer Authenticated Identities in SDK AWS v2.4.16 I have a ILDeveloperAuthenticatedIdentityProvider (:AWSCognitoCredentialsProviderHelper ) where has implemented “(AWSTask<NSString > )token” among others like AWS Doc explain, but this “token” function never called. Why could this be happening?
— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.
@donileo i don't suggest you use the approach that you suggested
- (AWSTask *)logins
{
if (![WinkAPI sharedManager].session) {
return [AWSTask taskWithError:[NSError errorWithDomain:kWinkErrorDomain code:1 userInfo:@{NSLocalizedDescriptionKey:@"Unable to get a proper logins map, no valid wink session found"}]];
}
// NOTE: here we are using the AWSIdentityProviderAmazonCognitoIdentity which is @"".
// NEVER send back the identity provider name as the key here. Thats only ever meant to be used in the
// generation of the cognito token in the backend.
return [AWSTask taskWithResult:@{AWSIdentityProviderAmazonCognitoIdentity: [WinkAPI sharedManager].session.awsToken.uid}];
}
is more of an approach used in older release (2.3.6 and before). It doesnt apply and will not work well if you use a 2.4.0 or higher version of sdk. From 2.4.0 the logins method should always return a current token. If you cache the token then it will eventually expire and the sdk will only call logins when it is refreshing the credentials.
I suggest you implement it the way @behrooziAWS suggested a couple of comments before yours by just overriding the method - (AWSTask <NSString*>) token
which will make call to your backend and fetch the current token. You can read more about it here
@karthiksaligrama the approach I put above is based on analyzing how the SDK currently works in 2.4.0. I am overriding the logins method in the IdentityProviderManager not the IdentityProvider. In the SDK, when using the enhanced flow (non-api) the SDK will call the IdentityProviderManager's login method after it refreshes the token via the (getIdentityId call of the identityProvider - making a backend call) from the IdentityProvider.
As far as what I'm noticing thats what I'm supposed to do. Get a valid token, store it in [WinkAPI sharedManager].session. When the token expires (an expiration error condition happens in the SDK) my Identity provider will refresh the token and my [WinkAPI sharedManager].session will have the new token. Then the logins method in my provider manager will return a valid token. I have not been seeing any issues whatsoever with my method.
Also, if you override the - (AWSTask <NSString*>) token that method doesn't even get called unless you are in the non-enhanced flow of the IdentityProvider which by default you are not. Keep in mind the IdentityProviderManager MUST be implemented and passed in when initializing your custom identity provider via initWithRegionType:identityPoolId:useEnhancedFlow:identityProviderManager:.
Please see the lines starting with 262 in AWSIdentityProvider.m:
if (self.identityProviderManager && self.useEnhancedFlow) {
self.cachedLogins = nil;
return [[self getIdentityId] continueWithSuccessBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
if(self.cachedLogins){
return [AWSTask taskWithResult:self.cachedLogins];
}
else {
return [self.identityProviderManager logins];
}
}];
}
return [[self token] continueWithSuccessBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
if (!task.result) {
return [AWSTask taskWithResult:nil];
}
NSString *token = task.result;
return [AWSTask taskWithResult:@{self.identityProviderName : token}];
}];
@donileo i get that. You really shouldnt be setting the identityProviderManager at all. You should just extend the AWSCognitoCredentialsProviderHelper
and override the method - (AWSTask *)token
. If you override the IdentityProviderManager and return a cached token then you will run into problems. The token that you get from Cognito GetOpentIdTokenForDeveloperIdentity
is only valid for 15 mins. If you are caching anything to not require the user to provide the user name and password again you should be caching a refresh token against your backend and use that refresh token and get an new OpenIdToken by calling GetOpentIdTokenForDeveloperIdentity
each time.
When ever you are using developer authenticated identities you need to use the constructor.
- (instancetype)initWithRegionType:(AWSRegionType)regionType
identityProvider:(id<AWSCognitoCredentialsProviderHelper>)identityProvider;
@karthiksaligrama what is the IdentityProviderManager for then? Yes, if you were to use that method above only and not provide an identityProviderManager then yea you should implement the tokens method in your identityProvider. As far as my code, I did implement an IdentityProviderManager and of course when you do you should implement the logins method in your provider manager. Can you please explain whats wrong with my method. I believe both are fine. Seems like you are implying the token returned by the IdentityProvider tokens method is not the same as the one returned in the logins method of the IdentityProviderManager. As far as my backend, yes my backend does call GetOpentIdTokenForDeveloperIdentity and provide my client code with a new AWS token essentially refreshing my token, it does it successfully when it expires. What triggers the call? The AWS SDK detects when the token has expired, and places my backend call to refresh/provide a new token by calling GetOpentIdTokenForDeveloperIdentity. Don't see anything wrong there.
The IdentityProviderManager is for when you are not using developer provided identities. If you are using developer provider identities at all, then the logins method on AWSCognitoCredentialsProviderHelper serves the role as the IdentityProviderManager.
@behrooziAWS got it :). As far as my code it still works, just that my refresh token call should also be in the token method and theres no need for my Custom IdentityProviderManager. Thanks! I'll update the post I made up above.
@behrooziAWS Where is this differentiation on AWSIdentityProviderManager
and AWSCognitoCredentialsProviderHelper
noted in the documentation?
Are there any code samples that demonstrate how to use either (or both) of them as intended?
@kdbertel @behrooziAWS also if the AWSIdentityProviderManager is not part of the developer provider identities flow at at all why is it being asked for on the initialization to the AWSCognitoCredentialsProviderHelper? Please see method: initWithRegionType:identityPoolId:useEnhancedFlow:identityProviderManager:
@donileo It is not required. If you have an identity provider manager that implements all of your other non developer authenticated providers, you can call it in the logins method in your AWSCognitoCredentialsProviderHelper.
To avoid further confusion, no one from AWS will continue to respond to this thread. There are several implementations in the comments that fail for certain edge cases, don't properly refresh tokens, are more complicated than necessary or are for unrelated issues. Too many people are arriving at this thread, not following the official guidance and ending up with issues. The official guidance is here: http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html Please open a new issue if you have issues with the official guidance.
This was either hard to find or did not exist until recently. Thank your for putting this up here.
On Jan 12, 2017, at 1:49 PM, David Behroozi notifications@github.com wrote:
To avoid further confusion, no one from AWS will continue to respond to this thread. There are several implementations in the comments that fail for certain edge cases, don't properly refresh tokens, are more complicated than necessary or are for unrelated issues. Too many people are arriving at this thread, not following the official guidance and ending up with issues. The official guidance is here: http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html Please open a new issue if you have issues with the official guidance.
— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.
@behrooziAWS I'm not stating that it "is" required. Simply, if its not, why surface it up so high up to designated initializer of the AWSCognigoCredentialsProviderHelper. The fact that its there makes it seem that it is. The official docs? Let us all hope they are good now :/.
@behrooziAWS sorry! but your comment is not serious:
Firstly, I know this link because in a issue ticket, a AWS support guy told my team we should try with @donileo information. After your comment, that response mysteriously disappeared.
Secondly, Open new issue? we have opened dozens tickets about cognito and IOS, We always receive the same reponse, "are you use clearKeychain? are you instantiating 2 sync clients? ahh OK!!!! I have passed the information to the Cognito Team". I opened a ticket in October and the last weekend (3 months) the support team guy ask us again... "are you use clearKeychain?" OMG!!!!!
Do you understand why people are arriving at this thread?
@drsromero exactly. Theres a reason why this thread exists and is the amount of comments you see; horrible documentation from AWS. My original post detailing how to properly implement the auth flow was the best that I could implement based on what I knew, and what I discovered debugging the SDK. I also figured I'd try and help others given how crazy this issue had become. After they provided new info (that the manager isn't for dev authenticated flows) it changed a lot of the information I had posted and the post is now updated to match this simpler flow. But if the manager is used then all the info in my original post all applies again. Again its not a coincidence this thread is what it is.
Btw, looking at the docs @behrooziAWS posted it seems that the IdentityProviderManager is to be used during dev federated identities when you want to provide auth for the standard providers as well. He seemed to imply the manager is not part of the flow at all, which definitely contradicts the link he posted. Once you add the manager, like in this case, I'd say my original post applies again.
God, after spending HOURS trying to solve "Invalid token" error I've noticed that using wrong initializer for AWSCognitoCredentialsProvider.
It should be initWithRegionType:identityProvider: (not initWithRegionType:identityPoolId:identityProviderManager: !!!).
Also, there is no any difference between [logins] and [token] overriding for developer identities. [token] is just helper to automatically build login map like @{ self.identityProviderName : token }
And, yes, it's better to pass self.identityProviderName to get logins key for developer identities since it returns "cognito-identity.amazonaws.com"
Hope it helps someone in trouble...
So I spent ages getting a custom login provider to work. I have a class BYOIProvider extending AWSAbstractCognitoIdentityProvider based on some AWS sample. This was all working fine. However, upgrading to 2.4.0 the AWSAbstractCognitoIdentityProvider is no longer available? Do I have to rewrite this code now?