How do you pass Authorization header through API Gateway to HTTP endpoint?

32,067

Solution 1

As noted in comments, the Authorization header includes incomplete information for you to establish who the user is, so I wouldn't recommend going this route. Additionally, if AWS_IAM auth is enabled, the Authorization header will be consumed by API Gateway.

If AWS_IAM auth is enabled and the signature is supplied correctly, the $context.identity parameters should reflect the credentials used to sign the request.

If you use the test invoke feature in the console, do you see the context fields being filled in?

Update: I'm unable to reproduce this issue. I have an API with the following mapping template:

#set($path = $input.params().path)
#set($qs = $input.params().querystring)
{
    "resource-path": "$context.resourcePath",
    "http-method": "$context.httpMethod",
    "identity": {
        #foreach($key in $context.identity.keySet())
            "$key": "$context.identity.get($key)"
        #if($foreach.hasNext), #end
        #end
    },
    "params": {
        #foreach($key in $path.keySet())
            "$key": "$path.get($key)"
        #if($foreach.hasNext), #end
        #end
    },
    "query": {
        #foreach($key in $qs.keySet())
            "$key": "$qs.get($key)"
        #if($foreach.hasNext), #end
        #end
    },
    "body": $input.json('$')
}

And a lambda function that simply spits back the input as output. When I sign the request and invoke the API, I get back the expected results:

{
  "resource-path":"/iam",
  "http-method":"GET",
  "identity":{ 
    "cognitoIdentityPoolId":"",
    "accountId":"xxxxxxxx",
    "cognitoIdentityId":"",
    "caller":"AIDXXXXXXXXXXX,
    "apiKey":"",
    "sourceIp":"54.xx.xx.xx",
    "cognitoAuthenticationType":"",
    "cognitoAuthenticationProvider":"",
    "userArn":"arn:aws:iam::xxxxxxxx:user/hackathon",
    "userAgent":"Java/1.8.0_31",
    "user":"AIDXXXXXXXXXXXXXX"
  },
  "params":{},
  "query":{},
  "body":{}
}

Solution 2

Currently the Authorization header can only be forwarded for methods that do not require AWS authentication. The SigV4 signing process relies on the Authorization header and we do not expose this for security purposes. If you have data you need to send (besides the SigV4 signature), you would need to send in another header.

Solution 3

In AWS API Gateway, Request Body is not supported for GET methods.

Solution 4

In Integration Request convert your GET to POST by specifying POST as your HTTP method. Then proceed with specifying the Body Mapping Template as proposed by @BobKinney

This way the request body will propagate properly, but the client will still be making a GET request as expected

Share:
32,067

Related videos on Youtube

Nick
Author by

Nick

Updated on March 22, 2020

Comments

  • Nick
    Nick over 2 years

    I have an API behind an AWS API Gateway that needs to use the Authorization header for processing. I have unfortunately been unable to pass this to the backend for processing.

    I have tried creating the Authorization HTTP Request Header in my Method Request and then creating the corresponding Authorization HTTP Header in my Integration Request (Authorization is mapped from method.request.header.Authorization in this case). I log all of the headers that the backend receives, and from the log, I can see other headers that I have listed in the Integration Request but not Authorization.

    I have also tried creating a mapping template with Content-Type application/json and the template defined as

      {
          "AccountID": "$context.identity.accountId",
          "Caller": "$context.identity.caller",
          "User": "$context.identity.user",
          "Authorization": "$input.params().header.get('Authorization')",
          "UserARN": "$context.identity.userArn"
      }
    

    Yet, the backend logs show that there is still no Authorization header nor any Authorization field in the JSON body. I also cannot see the user's ARN. I have seen other examples and threads where users have mentioned accessing the Authorization field on the event object that is passed into a Lambda function, but I am not using a Lambda function.

    I have made sure to Deploy the API Gateway in both scenarios.

    Does anyone know if there is some way I can pass the Authorization header through the API Gateway to my HTTP endpoint? Is there an alternative way to access the API caller's user name or ID?


    Edit - Here's a snippet of the code I'm using to hit the API Gateway:

    String awsAccessKey = "myaccesskey";
    String awsSecretKey = "mysecretkey";
    
    URL endpointUrl;
    try {
        endpointUrl = new URL("https://<host>/<path>/<to>/<resource>?startDate=20151201&endDate=20151231");
    } catch(Exception e) {
        throw new RuntimeException("Unable to parse service endpoint: " + e.getMessage());
    }
    
    Date now = new Date();
    
    SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
    sdf1.setTimeZone(new SimpleTimeZone(0, "UTC"));
    String dateTS = sdf1.format(now);
    
    String headerNames = "host;x-amz-date";
    String queryParameters = "endDate=20151231&startDate=20151201";
    
    String canonicalRequest = "GET\n" +
            "/<path>/<to>/<resource>\n" +
            queryParameters + "\n" +
            "host:<host>\n" +
            "x-amz-date:" + dateTS + "\n" +
            "\n" +
            headerNames + "\n" +
            "<sha256 hash for empty request body>";
    
    System.out.println(canonicalRequest);
    
    SimpleDateFormat sdf2 = new SimpleDateFormat("yyyyMMdd");
    sdf2.setTimeZone(new SimpleTimeZone(0, "UTC"));
    String dateStr = sdf2.format(now);
    String scope =  dateStr + "/us-east-1/execute-api/aws4_request";
    String stringToSign =
            "AWS4-HMAC-SHA256\n" +
                   dateTS + "\n" +
                   scope + "\n" +
                   "hex encoded hash of canonicalRequest";
    
    System.out.println(stringToSign);
    
    byte[] kSecret = ("AWS4" + awsSecretKey).getBytes();
    byte[] kDate = HmacSHA256(dateStr, kSecret);
    byte[] kRegion = HmacSHA256("us-east-1", kDate);
    byte[] kService = HmacSHA256("execute-api", kRegion);
    byte[] kSigning = HmacSHA256("aws4_request", kService);
    byte[] signature = HmacSHA256(stringToSign, kSigning);
    String credentialsAuthorizationHeader = "Credential=" + awsAccessKey + "/" + scope;
    String signedHeadersAuthorizationHeader = "SignedHeaders=" + headerNames;
    String signatureAuthorizationHeader = "Signature=" + "hex encoded signature";
    String authorization = "AWS4-HMAC-SHA256 "
            + credentialsAuthorizationHeader + ", "
            + signedHeadersAuthorizationHeader + ", "
            + signatureAuthorizationHeader;
    
    Map<String, String> headers = new HashMap<String, String>();
    headers.put("x-amz-date", dateTS);
    headers.put("Host", endpointUrl.getHost());
    headers.put("Authorization", authorization);
    headers.put("Content-Type", "application/json");
    
    HttpURLConnection connection = null;
    try {
        connection = (HttpURLConnection) endpointUrl.openConnection();
        connection.setRequestMethod("GET");
    
        for (String headerKey : headers.keySet()) {
            connection.setRequestProperty(headerKey, headers.get(headerKey));
        }
        connection.setUseCaches(false);
        connection.setDoInput(true);
        connection.setDoOutput(true);
    
        InputStream is;
        try {
            is = connection.getInputStream();
        } catch (IOException e) {
            is = connection.getErrorStream();
        }
    
        BufferedReader rd = new BufferedReader(new InputStreamReader(is));
        String line;
        StringBuffer response = new StringBuffer();
        while ((line = rd.readLine()) != null) {
            response.append(line);
            response.append('\r');
        }
        rd.close();
        System.out.println(response.toString());
    } catch (Exception e) {
        throw new RuntimeException("Error: " + e.getMessage(), e);
    } finally {
        if (connection != null) {
            connection.disconnect();
        }
    }
    

    This is good enough to authenticate successfully and hit the HTTP endpoint on the backend.

    • Bob Kinney
      Bob Kinney over 6 years
      Just to clarify your goals, do you want the Auhthorization header or the result of the AWS authentication? Did you enable AWS_IAM auth for the method?
    • Nick
      Nick over 6 years
      I would like the Authorization header, which contains signature, credentials, etc. I could also use some info about the caller (e.g. User ID) if Authentication header cannot be passed through. Yes, AWS_IAM is enabled for the resource.
    • Bob Kinney
      Bob Kinney over 6 years
      What authentication method are you using to access the API? IAM user credentials, temporary STS role based credentials, Cognito credentials?
    • Nick
      Nick over 6 years
      We are having users use the IAM user credentials to create the signature which the user then adds to the headers of their request. Another question: if my end goal is to figure out the name of the IAM user who is authenticated and is using the API Gateway, can we use the authentication results to do that in any way?
    • Bob Kinney
      Bob Kinney over 6 years
      No, the Authorization header would likely be useless to you as the only thing included is the AcessKey and signature. You'd have to implement your own system for mapping that AccessKey id to a given user.
    • Lijju Mathew
      Lijju Mathew almost 2 years
      I tried using above code and it is failing with error "{"message":"'encoded' not a valid key=value pair (missing equal-sign) in Authorization header: 'AWS4-HMAC-SHA256 Credential=XXXXXXXXXXX/20201105/us-east-1/execute-api/aws4_r‌​equest, SignedHeaders=host;x-amz-date, Signature=hex encoded signature'."} . Any idea what I am missing. Do we have to substitute anything for "<sha256 hash for empty request body>" in cannocial request ?
  • Nick
    Nick over 6 years
    Thanks for the feedback so far. I was going to get the AccessKey from the Authorization header, iterate through our users and try to find one that has a matching AccessKey. However, it sounds like that won't work since the Authorization header gets consumed when AWS_IAM auth is enabled. I tried the suggestion made with the $context.identity parameters. Using the test invoke feature, I can see them being logged by my API. However, when I hit the API Gateway programatically, the same API is hit but the context.identity fields are not populated and logged. Why only for the test feature?
  • Bob Kinney
    Bob Kinney over 6 years
    @Nick that definitely shouldn't be happening. Are you positive you are signing requests? Could you post the code you are using to invoke the API?
  • Nick
    Nick over 6 years
    I updated the question with a code snippet. I am sure that I am signing requests.
  • Bob Kinney
    Bob Kinney over 6 years
    @Nick I apologize, but I'm unable to reproduce this issue. Please make sure you have deployed the API with the updated template and have AWS_IAM auth enabled.
  • Nick
    Nick over 6 years
    I tried using the mapping template that you provided and also double checked that the API was deployed with AWS_IAM auth enabled. Unfortunately, I see the same result: I can see the expected fields in the request body when logged by my API after hitting the API Gateway using the Test functionality. When I hit the API Gateway using my code provided, the request goes through to my API and the request body is logged as empty. All headers and the query string look the same, except for x-forwarded-for IP. Perhaps I can try it with a different endpoint and see what happens sometime...
  • Ka Hou Ieong
    Ka Hou Ieong over 6 years
    Yes, API Gateway doesn't support request body for GET method on the integration side.