8

I have problems getting the authorization of my API on AWS for a Cognito User Pool via HTTP headers (without AWS API Gateway SDK) to work.

My setup:

On AWS:

  • A REST API implemented on AWS Lambda (deployed via Serverless framework),
  • exposed via API Gateway using type LAMBDA_PROXY (no manual mapping)
  • Authorization on API Gateway via the provided "Cognito User Pool authorizer" (no "AWS_IAM" option, no custom coded authorizer)
  • Testing the API via Postman

On the iOS client

  • Registration/Sign-In via AWS Cognito (SDK and UI copied from the AWS Mobile Hub generated demo Xcode project)
  • Accessing the REST API via RestKit, not using the AWSAPIGateway SDK

What is working:

The API methods get properly deployed via serverless.

I can call the public (not set to use the user pool) via Postman.

For the private API methods, I can see the Cognito user pool authorizer set up in the API Gateway management console, including "Identity token source" set to method.request.header.Authorization (the default) as described here

On iOS, I can properly register and log in as user. I can dump the AWS Credentials details to the console, showing AccessKey, SecretKey and SessionKey.

On iOS, I can query the public API via RestKit.

When I try to call a private API method via Postman, I get back an HTTP error 401 with the body {"message": "Unauthorized"}. (Which is expected, without setting any authorization.)

What fails:

To test the authorization, in Postman, I have tried

The result was always the same 401 error.

What do I need to set as HTTP headers, in order to authorize the calls to the private API? "Authorization" should work - maybe I am missing some role permissions?

How can I better debug the permissions / authorization flow?

thomers
  • 2,603
  • 4
  • 29
  • 50
  • 1
    you mention use of `SessionKey`; is that in reference to one of `ID token` or `Access Token`? in other terms it should be a `JSON Web Key Token` as far as I am aware. as per http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html – Peter Pajchl Feb 01 '17 at 15:04
  • Thanks Peter - you seem to be right, there are two kind of tokens in use, and it was not clear for me which one to use. If the JWT is expected, I did not find any info on how to get it (there is a method in the Android SDK http://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-android-sdk.html , but not in the iOS SDK http://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-ios-sdk.html ) – thomers Feb 01 '17 at 15:56
  • 1
    I take it that you sign in your user with a method similar to `getSession(username, password: password, validationData: nil, scopes: nil)` which should return a `session` object of type `AWSCognitoIdentityUserSession`; token is accessible as `session.accessToken?.tokenString` – Peter Pajchl Feb 01 '17 at 21:38
  • 1
    I'm using the code from the AWS generated demo project. It uses `[[AWSIdentityManager defaultIdentityManager] loginWithSignInProvider:[AWSCognitoUserPoolsSignInProvider sharedInstance] completionHandler:...]` I thought I'm saving some time by reusing the code and UI from the AWS demo, but I'm beginning to regret this decision... – thomers Feb 02 '17 at 08:57
  • @PeterPajchl you were right, and I found out how to get the session: `AWSCognitoIdentityUserPool *pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:AWSCognitoUserPoolsSignInProviderKey]; AWSCognitoIdentityUser *user = [pool currentUser]; AWSTask *task = [user getSession];`. Then, I can use `task.result.idToken.tokenString as `Authorization` HTTP header, and it works. – thomers Feb 02 '17 at 15:10
  • (If you want to write this as an answer, I will accept it.) – thomers Feb 02 '17 at 15:11

3 Answers3

2

This is how to get the session:

AWSCognitoIdentityUserPool *pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:AWSCognitoUserPoolsSignInProvi‌​derKey]; 
AWSCognitoIdentityUser *user = [pool currentUser]; 
AWSTask<AWSCognitoIdentityUserSession *> *task = [user getSession];

Then, task.result.idToken.tokenString can be set as "Authorization" header, and it works.

Thanks Peter for the tip!

thomers
  • 2,603
  • 4
  • 29
  • 50
2

For iOS (As of 11 May 2017)

 let headerParameters = [
               "Content-Type": "application/json",
               "Accept": "application/json",
               "AccessKey" : awsCredentials.accessKey,
               "SecretKey" : awsCredentials.secretKey,
               "SessionKey" : awsCredentials.sessionKey,
               "Authorization" :
                AWSCognitoUserPoolsSignInProvider.sharedInstance().getUserPool().currentUser()?.getSession().result?.idToken?.tokenString
            ]

where awsCredentials you can obtain on successful login and store it somewhere in your own code (AuthorizationService?). I kept the "Authorization" value so long so that it is easier for developers to know the full location of this hidden object. You should be keeping it in your own AuthorizationService class (or as extension member to AwsCredentials?)

Kajal Sinha
  • 1,565
  • 11
  • 20
  • Thanks - this is basically what I described below. I did not need to set the `AccessKey`, `SecretKey` and `SessionKey` headers, just `Authorization` was sufficient. – thomers May 11 '17 at 11:14
1

If you use Swift 3, you can get identity token by the code below.

        let pool = AWSCognitoUserPoolsSignInProvider.sharedInstance().getUserPool()
        let currentUser = pool.currentUser()
        let task = currentUser?.getSession()
        let identityToken = task?.result?.idToken?.tokenString
        print("identityToken: \(String(describing: identityToken))")
Yi-Hsiu Lee
  • 176
  • 2
  • 5