AWS api gateway returning token in the response body - amazon-web-services

I noticed something weird when calling my service on a URL like this
GET https://myservice.com//someresource
--header 'Content-Type: application/json' \
--header 'x-api-key:<somekey>' \
--header 'Authorization: Bearer <sometoken>
When I do this, I get a response from AWS gateway that includes in the response body
< HTTP/2 403
< content-type: application/json
< content-length: 3222
< x-amzn-requestid: 348fab78-b84d-4af9-88v9-e1e6effc487b
< x-amzn-errortype: IncompleteSignatureException
< x-amz-apigw-id: dgYbMFbhliAFv8w=
<
* Connection #0 to host api-aws-tst.reprisk.com left intact
{"message":"<sometoken>"}
Calling https://myservice.com/someresource , without the extra slash, works ok. I know that // is not a correct path but I would like that the response message reflect the fact that the path is wrong instead of returning my token in a 403 response.
Is there any setting to configure this behaviour?

Is there any setting to configure this behavior?
Yes. You can set up integration response mappings to modify the responses of API Gateway. In your case you need to have an integration response mapping for HTTP 403.
These docs explain how the mapping works and how to set them:
Set up an integration response in API Gateway
Use a mapping template to override an API's request and response parameters and status codes

Related

Cognito authorization endpoint (without client secret) returning Invalid client error message

I have a problem with Cognito and api clients like Postman or Insomnia.
There is a mobile app that makes calls to the backend.
There is an AWS Cognito instance, with one user pool and one API client, configured for using Authorization Code, with Cognito User Pool set as an Identity Provider
At first, the API client was configured to use client secret. I was able to make API calls from Postman or Insomnia using Oauth2 authentication, but for some unknown reason I wasn't able to authenticate using the mobile app
Then there was a change in the infrastructure - the old API Client entry in Cognito was recreated, but configured NOT to use client secret. We immediately removed client secret data from the code. After that, I was able to log in from the mobile application and send requests to the backend, but now I cannot authenticate with Postman/Insomnia. The browser window is opening, I can see the credentials form, I can properly login, but after that when Postman is calling the token endpoint, I get a browser window with one message in it:
An error was encountered with the requested page.
And I do not receive my tokens. Postman says:
Authentication failed
Couldn’t complete authentication. Check the Postman Console for more details.
Insomnia:
[oauth2] Failed to fetch token url=https://my-app-address.amazoncognito.com/oauth2/token status=400
And finally, here's the Insomnia's response timeline:
* Preparing request to https://my-app-name.auth.eu-west-1.amazoncognito.com/oauth2/token
* Current time is 2023-01-04T10:56:38.314Z
* Enable automatic URL encoding
* Using default HTTP version
* Enable timeout of 30000ms
* Enable SSL validation
* Enable cookie sending with jar of 2 cookies
* Found bundle for host my-app-name.auth.eu-west-1.amazoncognito.com: 0x12f2ee990 [can multiplex]
* Re-using existing connection! (#13) with host my-app-name.auth.eu-west-1.amazoncognito.com
* Connected to my-app-name.auth.eu-west-1.amazoncognito.com (x.x.x.x) port 443 (#13)
* Using Stream ID: 9 (easy handle 0x14e9f8400)
> POST /oauth2/token HTTP/2
> Host: my-app-name.auth.eu-west-1.amazoncognito.com
> user-agent: insomnia/2022.7.0
> cookie: XSRF-TOKEN=f1264cb4-b688-41a3-9126-cf021df2fa30
> content-type: application/x-www-form-urlencoded
> accept: application/x-www-form-urlencoded, application/json
> authorization: Basic MzJkN2JvnzNhNUTzNGNIN3UzdjY5b3Zkb246ZXI=
> content-length: 166
| grant_type=authorization_code&code=9e3e6ae8-30c7-6c5c-9aee-1930131a6624&redirect_uri=myapp%3A%2F%2Ffrontpage&code_verifier=dhQD1EtvMm_yP6eGorgQU7budSloaspeuGUM_OzS34k
* We are completely uploaded and fine
< HTTP/2 400
< date: Wed, 04 Jan 2023 10:56:38 GMT
< content-type: application/json;charset=UTF-8
< x-amz-cognito-request-id: 71156586-7bd3-4485-944c-07b5b930ce15
< x-content-type-options: nosniff
< x-xss-protection: 1; mode=block
< cache-control: no-cache, no-store, max-age=0, must-revalidate
< pragma: no-cache
< expires: 0
< strict-transport-security: max-age=31536000 ; includeSubDomains
< x-frame-options: DENY
< server: Server
* Received 26 B chunk
* Connection #13 to host my-app-name.auth.eu-west-1.amazoncognito.com left intact
| {"error":"invalid_client"}
And the cognito user pool config:
resource "aws_cognito_user_pool_client" "my_app_client" {
name = "my-app-dev"
user_pool_id = aws_cognito_user_pool.default.id
allowed_oauth_flows = ["code"]
allowed_oauth_flows_user_pool_client = true
allowed_oauth_scopes = ["email", "openid"]
callback_urls = ["myapp://frontpage"]
logout_urls = ["myapp://signout"]
supported_identity_providers = ["COGNITO"]
}
What could be the reason why I am unable to authenticate using Postman/Insomnia and receive such errors? Could this be something related to AWS configuration? Or I'm doing something wrong?
As you have not configured the client secret in the App client, authorization header should not be added in the token request.
As mentioned in the document, authorization header needs to be provided only if the client was issued a secret.

To change the status code from 200 to 404 when stepfunction not found

I'm trying to Execute the AWS step function from API Gateway, It's working as expected.
Whenever I'm passing the input, statemachinearn(stepfunction name to execute) It's triggering the step function.
But It's still returning the status code 200, whenever it's not able to find the stepfunction, I want to return the status code 404 if the apigateway not found that stepfunction.
Could you please help me on that
Response:
Status: 200ok
Expected:
Status: 404
Thanks,
Harika.
As per the documentation StartExecution API call do return 400 Bad Request for non existent statemachine which is correct as RESTful API standard.
StateMachineDoesNotExist
The specified state machine does not exist.
HTTP Status Code: 400
From the RESTful API point of view, endpoint /execution/(which I created in API Gateway for the integration setup) is a resource, no matter it accepts GET or POST or something else. 404 is only appropriate when the resource /execution/ itself does not exist. If /execution/ endpoint exists, but its invocation failed (no matter what the reasons), the response status code must be something other than 404.
So in the case of the returned response(200) for POST call with non-existent statemachine it is correct. But when API Gateway tried to make the call to non-existent statemachine it got 404 from StartExecution api call which it eventually wrapped into a proper message instead of returning 404 http response.
curl -s -X POST -d '{"input": "{}","name": "MyExecution17","stateMachineArn": "arn:aws:states:eu-central-1:1234567890:stateMachine:mystatemachine"}' https://123456asdasdas.execute-api.eu-central-1.amazonaws.com/v1/execution|jq .
{
"__type": "com.amazonaws.swf.service.v2.model#StateMachineDoesNotExist",
"message": "State Machine Does Not Exist: 'arn:aws:states:eu-central-1:1234567890:stateMachine:mystatemachine1'"
}
Let's say you create another MethodResponse where you can provide an exact HTTP Status Code in your case 404 which you want to return and you do an Integration Response where you have to choose the Method Response by providing either Exact HTTP Responce Code(400 -> Upstream response from the **StartExecution** API Call) OR a Regex -> (4\{d}2) matching all the 4xx errors.
In that case you will be giving 404 for all the responses where the upstream error 4xx StartExecution Errors
ExecutionAlreadyExists -> 400
ExecutionLimitExceeded -> 400
InvalidArn -> 400
InvalidExecutionInput -> 400
InvalidName -> 400
StateMachineDeleting -> 400
StateMachineDoesNotExist -> 400
Non Existent State Machine:
curl -s -X POST -d '{"input": "{}","name": "MyExecution17","stateMachineArn": "arn:aws:states:eu-central-1:1234567890:stateMachine:mystatemachine1"}' https://123456asdasdas.execute-api.eu-central-1.amazonaws.com/v1/execution|jq .
< HTTP/2 404
< date: Sat, 30 Jan 2021 14:12:16 GMT
< content-type: application/json
...
{
"__type": "com.amazonaws.swf.service.v2.model#StateMachineDoesNotExist",
"message": "State Machine Does Not Exist: 'arn:aws:states:eu-central-1:1234567890:stateMachine:mystatemachine1'"
}
Execution Already Exists
curl -s -X POST -d '{"input": "{}","name": "MyExecution17","stateMachineArn": "arn:aws:states:eu-central-1:1234567890:stateMachine:mystatemachine"}' https://123456asdasdas.execute-api.eu-central-1.amazonaws.com/v1/execution|jq .
* We are completely uploaded and fine
< HTTP/2 404
< date: Sat, 30 Jan 2021 14:28:27 GMT
< content-type: application/json
{
"__type": "com.amazonaws.swf.service.v2.model#ExecutionAlreadyExists",
"message": "Execution Already Exists: 'arn:aws:states:eu-central-1:1234567890:execution:mystatemachine:MyExecution17'"
}
Which I think will be misleading.

generate access token using Postman

I have written API using Django REST Frameword and Django oAuth Toolkit for oauth2 authentication and using Postman to test my API authorization process.
I have to send following curl request
curl -X POST -d "grant_type=password&username=<user>&password=<password>" -u "<client_id>:<client_secret" http://127.0.0.1:3333/auth/token/
I can generate access_token simply using Postman Get Access Token window
But I want to do it by sending a request and passing data using request form, so that I could test the API and also generate the documentation for auth.
Now, I can pass user data (username, password) in form-data but how to pass client_id and client_secret?
For a full Postman answer, the way to accomplish this is with a pre-request script. The client id and the client secret are simply encoded with the base64 encoding scheme. Just do this:
Notice that client_id_client_secret is an environment variable. If you don't want to do that, then drop the first line and hard-code your client id and secret into CryptoJS.enc.Utf8.parse('my-trusted-client:mysecret'), where 'my-trusted-client' is the client id and 'mysecret' is the client secret.
Here's the code for copy/paste joy.
let keys = pm.environment.get('client_id_client_secret');
let encoded = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(keys));
pm.environment.set("base64_client_id_client_secret", encoded);
Now, create a header and include the variable you created:
The value part of that image:
Basic {{base64_client_id_client_secret}}
Now... just Postman bliss.
curl encrypts the value of -u parameter, which we can see using -v (verbose)option.
Therefore, to collect the header's authorization value, use -v once with the curl command. It will print the raw request as following:-
$ curl -X POST -d "grant_type=password&username=<user>&password=<password>" -u "client_id:client_secret" http://127.0.0.1:3000 -v
Note: Unnecessary use of -X or --request, POST is already inferred.
* Rebuilt URL to: http://127.0.0.1:3000/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
* Server auth using Basic with user 'client_id'
> POST / HTTP/1.1
> Host: 127.0.0.1:3000
> Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Length: 55
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 55 out of 55 bytes
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat, 19 May 2018 07:09:35 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
In the above verbose log, we can see the Key Value pairs as
> Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=
After collecting these key as "Authorization" and value as "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=", you can use them in headers of the request through postman. "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=" is the encrypted value generated using the -u "client_id:client_secret" option with curl.
Hope this will solve the auth problem using postman request.

Nginx+uWSGI+Django are returning 502 when big request body and expired session

I have a Django view that process POST request with random size(between 20 char to 30k char). This API is only available for registered users and is validated with a session header. The API works well with my test cases but I notice some 502 in the Nginx log. The error log show this line::
2016/12/26 19:53:15 [error] 1048#0: *72 sendfile() failed (32: Broken pipe) while sending request to upstream, client: XXX.XXX.XXX.XXX, server: , request: "POST /api/v1/purchase HTTP/1.1", upstream: "uwsgi://unix:///opt/project/sockets/uwsgi.sock:", host: "staging.example.com"
After some tests, I managed to recreate this call with a big body request.
curl -XPOST https://staging.example.com/api/v1/purchase \
-H "Accept: application/json" \
-H "token: development-token" \
-H "session: bad-session" \
-i -d '{"receipt-data": "<25677 character string>"}'
HTTP/1.1 100 Continue
HTTP/1.1 502 Bad Gateway
Server: nginx/1.4.6 (Ubuntu)
Date: Mon, 26 Dec 2016 19:54:32 GMT
Content-Type: text/html
Content-Length: 181
Connection: keep-alive
<html>
<head><title>502 Bad Gateway</title></head>
<body bgcolor="white">
<center><h1>502 Bad Gateway</h1></center>
<hr><center>nginx/1.4.6 (Ubuntu)</center>
</body>
</html>
What it seems to happen is that the Django checks that the session is not valid and return the response(403) before the client finish delivers the body.
If I'm correct, is there a way to make Django send that 100 status after checking the headers instead of the Nginx?
If not, is there a more elegant solution than wait for the body before checking the headers?
I've found a statement that adding HTTP header connection:keep-alive to the client should fix this issue. I'll verify it later, but already posting it here, hope it will help someone.

Using "Content-Type:application/json" to post in curl gives HTTP/1.1 400 BAD REQUEST

When I make a post request using the following
curl -i -d "username=rock&password=rock" http://my_VM_IP/api/1.1/json/my_login/
it generates the required response generating a token like this(abridged):
HTTP/1.1 200 OK
Date: Mon, 22 Oct 2012 08:37:39 GMT
Vary: Authorization,Accept-Language,Cookie,Accept-Encoding
Content-Type: text/plain
Transfer-Encoding: chunked
OK{"success": {"my_token": "required_token"}}
But when I try the same including a header as:
curl -i -H "Content-Type:application/json" -d "username=rock&password=rock" http://my_VM_IP/api/1.1/json/my_login/
it gives me the following error:
HTTP/1.1 400 BAD REQUEST
Date: Mon, 22 Oct 2012 11:12:04 GMT
Vary: Authorization,Accept-Language,Cookie,Accept-Encoding
***Content-Type: text/plain***
Content-Language: en-us
Connection: close
Transfer-Encoding: chunked
Bad Request
I dont understand why this happens. And also why does the content-Type show text/plain, I also tried looking at some other questions like Why Setting POST Content-type:"Application/Json" causes a "Bad Request" on REST WebService? . It also addresses the same problem I have. Following the answer in that I tried giving the data in various formats as
{"username":"rock", "password":"rock"}
but without success. Thanks in advance.
By using -H "Content-Type:application/json" you're setting the Content-Type header for your request. The response will still return whatever your view tells it to return.
To return a response with Content-Type application/json, use something along these lines:
import json
from django.http import HttpResponse
def json_response(return_vars):
'JSON-encodes return_vars returns it in an HttpResponse with a JSON mimetype'
return HttpResponse(json.dumps(return_vars), content_type = "application/json")
#Usage: return json_response({'admin_token': admin_api_token.token})
You were close, but you need to send it as a JSON format via curl:
curl -i -H "Content-Type:application/json" -d '{"username":"rock", "password":"rock"}'
("password","admin" should be "password":"admin")
If that's not working, try:
curl --dump-header - -H "Accept:application/json" -H "Content-Type:application/json" -X POST --data '{"username": "admin", "password": "admin"}' http://my_VM_IP/api/1.1/json/my_login/
When you set -H parameter of curl command, you specify content type of request. Content type of response, that you see in response, is set on the server. In WSGI application you need to specify 'content-type' and 'content-length' manually. Some of framework provide utility method to return JSON responses (for example, jsonify method in Flask).