I'm writing a serverless app with AWS (Lambda, API Gateway, Cognito, etc) and I find myself wondering how to best secure my stack.
I've read that for applications using a server, EC2 or otherwise, best practice is to keep user's ID tokens stored on the backend. This makes sense, since a node process would provide me a long term solution for hanging onto and reusing ID tokens. A serverless app on the other hand, does not provide this luxury. I've considered just keeping it on the front end- since after all, JWT tokens provided by cognito are signed, and should therefore be tamper proof, but this seems a bit unsettling from my end. I'd much prefer a system where users have no direct access to their own tokens. I've also thought about just requesting a new token for every request sent to Lambda, but this too seems like a far from perfect solution.
Is there some kind of accepted best practice surrounding serverless authentication and authorization? Am I on the right track just storing my tokens client side while the user has the app open?
I don't see an issue storing your tokens client side. The user can copy paste the token from the header request anytime. The token is not a secret. It can't be tampered because it's digitally signed.
For example bellow are the headers of a request.
In Authorization is stored the jwt token that can be decoded in https://jwt.io/
but it cannot be modified,
Host: aa.aa.aa
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer: token234567890-eddedede
X-Requested-With: XMLHttpRequest
Connection: keep-alive
In addition, it’s best practice to expire yr tokens and renew at certain intervals.
Related
The AWS S3 PUT REST API docs are lacking a clear example of the Authorization string in the Request Syntax.
Request Syntax
PUT /Key+ HTTP/1.1
Host: Bucket.s3.amazonaws.com
x-amz-acl: ACL
Cache-Control: CacheControl
Content-Disposition: ContentDisposition
Content-Encoding: ContentEncoding
Content-Language: ContentLanguage
Content-Length: ContentLength
Content-MD5: ContentMD5
Content-Type: ContentType
Expires: Expires
x-amz-grant-full-control: GrantFullControl
x-amz-grant-read: GrantRead
x-amz-grant-read-acp: GrantReadACP
x-amz-grant-write-acp: GrantWriteACP
x-amz-server-side-encryption: ServerSideEncryption
x-amz-storage-class: StorageClass
x-amz-website-redirect-location: WebsiteRedirectLocation
x-amz-server-side-encryption-customer-algorithm: SSECustomerAlgorithm
x-amz-server-side-encryption-customer-key: SSECustomerKey
x-amz-server-side-encryption-customer-key-MD5: SSECustomerKeyMD5
x-amz-server-side-encryption-aws-kms-key-id: SSEKMSKeyId
x-amz-server-side-encryption-context: SSEKMSEncryptionContext
x-amz-request-payer: RequestPayer
x-amz-tagging: Tagging
x-amz-object-lock-mode: ObjectLockMode
x-amz-object-lock-retain-until-date: ObjectLockRetainUntilDate
x-amz-object-lock-legal-hold: ObjectLockLegalHoldStatus
Body
The docs show this request example further on...
PUT /my-image.jpg HTTP/1.1
Host: myBucket.s3.<Region>.amazonaws.com
Date: Wed, 12 Oct 2009 17:50:00 GMT
Authorization: authorization string
Content-Type: text/plain
Content-Length: 11434
x-amz-meta-author: Janet
Expect: 100-continue
[11434 bytes of object data]
But again, the doc does not have an example format for Auth String. I tried AccessKeyID Secret but that didn't work. I dont' even see logical parameters in the request syntax to pass the two parts of the credential (AccessKeyID and Secret) anywhere in the examples!
Does anyone have a simple example of how to use PUT to add a .json file to S3 using the REST API? Preferrably a screenshot of PostMan setup to better explain where values go (in URL vs. as headers).
From the AWS docs here, it appears it is not possible to create a PUT request to an S3 bucket using REST API alone:
For authenticated requests, unless you are using the AWS SDKs, you have to write code to calculate signatures that provide authentication information in your requests.
This is a new concept to me. I've used token requests and sending keys in headers before when authenticating via REST API's. It sounds like a more secure method of auth.
I’m developing an Android App and a Web Service that communicate. My Web Service is in WEB API 2 with token bearer authentication.
My problem is that when I send too many requests (~20 request in 15 seconds) to my Web Service from my Android App, the WS response with
“401” : “Authorization has been denied for this request”
This happen ONLY on the production server (Amen hoster) AND from the Android Device. For example, if I try with Postman, everything works fine. So it’s related to my production server and/or my android app request.
The code for access to the Web Service
URL obj = new URL(SERVEUR_URL + url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
con.setRequestMethod("GET");
con.setRequestProperty("Authorization", "Bearer " + token);
con.setRequestProperty("Content-Type", "application/json");
int responseCode = con.getResponseCode();
String responseMessage = con.getResponseMessage();
The authentication provider on my Web Service is the default one. No modifications.
The request from my Android App (not work every time)
GET http://api.xxxx.com/api/Weesps/GetAvailableWeesps HTTP/1.1
Authorization: Bearer XXXX
Content-Type: application/json
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0; Google Nexus 5X - 6.0.0 - API 23 - 1080x1920 Build/MRA58K)
Host: api.xxxx.com
Connection: Keep-Alive
Accept-Encoding: gzip
The request from Postman (work every time)
GET http://api.xxxx.com/api/Weesps/GetAvailableWeesps HTTP/1.1
Host: api.xxxx.com
Connection: keep-alive
Authorization: Bearer XXXX
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
Postman-Token: bca55154-775d-9709-7a8b-4793393890ad
Accept: */*
Accept-Encoding: gzip, deflate, sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
Cookie: dadaproaffinity=14ff51cc869a14d3552485cb4ceee1faa1be7165cc5d4b0e2b19370f11afcbea
What I have tried:
Reproduce this error in local : it works fine on local server (web and SQL servers) from android app or from Postman
I check that the token was sent correctly in every requests
The request from Android is the same every time
Tried to add missing header to my android app request
I spend two days on this problem and read many stackoverflow posts but no one helps me.
Thanks for your help.
UPDATE 1 :
With Fiddler I saw that in GET request from Postman, they were a Cookie header. This cookie is sent when we ask for a bearer token.
Example of token response from the server
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 691
Content-Type: application/json;charset=UTF-8
Expires: -1
Server: Microsoft-IIS/8.5
Set-Cookie: .AspNet.Cookies=XXXX; path=/; HttpOnly
X-Powered-By: ASP.NET
X-Powered-By: ARR/2.5
Date: Tue, 31 May 2016 16:55:39 GMT
{"access_token":"XXXX","token_type":"bearer","expires_in":1209599,"userName":"Foo",".issued":"Tue, 31 May 2016 16:55:40 GMT",".expires":"Tue, 14 Jun 2016 16:55:40 GMT"}
Fiddler and Postman saved this cookie and they automatically put it in requests to API (example on the “The request from Postman” code block). When I remove the cookie from the Postman GET request, it doesn’t work (just like my android app).
Now, the question is: why WEB API 2 send a cookie instead of only using the token ? And why the token work great in the first requests and don’t work properly for the following requests ?
According to ASP.NET WebAPI2 flow you can see on the bottom of that page, it seems your requests are always authenticated but sometimes fail to get authorized.
So imo, the AuthorizationFilter[Authorize] rejects some of your requests for an unknown reason. What I would suggest is to dump the request your API receives as well as the claims identity attached to the token. Try to see if there is differences between them when you have a successful response and when you have a 401.
That way, you may be able to determine either it is your request that got malformed, if it is the claims identity that is not good or if it is the AuthorizationFilter that refuses you for another reason (like too much queries or else).
Good luck !
UPDATE 1
According to your new input, I think that your Web API is configured to use both token and cookie authentication.
What I see here is you have two solutions :
1°/ Store the returned cookie in your Android application and use it for next calls. Simplest and fastest way to solve your problem without changing all your API, but you store an authorization cookie : it can leads to security problem (CSRF attacks).
2°/ You can check how your authentication and authorization filters are set to disable cookie authentication and only rely on token authentication : it will hence forces all the requests and your API to only use token and will prevents you from suffering CSRF attacks. More complex because you have to dig into your web API configuration.
Check the following links (sorry, as I don't have enough reputation yet to post more than 2 links per post, you'll find them as text at the end of my answer) :
ASP.net Secure a Web API 2.2[2] : From the chapter "Configuring the Authorization Server" at the bottom
MSDN article on Web API security[3] : More general and technical information about web api security, how to secure it and CRSF attacks
StackOverflow .NET cookie and token authentication[4] : Check David Banister's answer, I think it is exactly what you want to do : Only use token for all your API calls.
StackOverflow Authorize filter and authentication[5] : More information about such mechanisms for your API
And finally
Cookie authentication with web API and 401 codes[6] : Sounds like your actual problem, isn't it ?
I hope it helps you, good luck !
// Links
2: www.asp.net/web-api/overview/security/individual-accounts-in-web-api
3: msdn.microsoft.com/en-us/magazine/dn201748.aspx
4: stackoverflow.com/questions/22568409/mvc-net-cookie-authenticated-system-acessing-a-web-api-with-token-authenticatio
5: stackoverflow.com/questions/21231751/authorize-filter-and-authentication
6: brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
Finally, I got my answer:
My Web Service send a Cookie named “dadaproaffinity” the first time I ask for a request. This Cookie was automatically put on the following request by Postman but not by Android HttpUrlConnection. So, I just take this Cookie and now I just add this Cookie on every requests with the Token.
But : This cookie is send by IIS, not by my Web Service ! That’s why it works on local but not on the production server. I googled this cookie and there are very few responses about that. The only one that I find in English is :
Technical Cookie of IIS Server hosting the site.
Need to route to the correct server session, in order to keep it active
Does anyone have more information about this IIS Cookie ?
First of all, I assume a backend that control inputs to prevent XSS vulnerabilities.
In this answer #Les Hazlewood explain how to protect the JWT in the client side.
Assuming 100% TLS for all communication - both during and at all times
after login - authenticating with username/password via basic
authentication and receiving a JWT in exchange is a valid use case.
This is almost exactly how one of OAuth 2's flows ('password grant')
works.
[...]
You just set the Authorization header:
Authorization: Bearer <JWT value here>
But, that being said, if your REST client is 'untrusted' (e.g.
JavaScript-enabled browser), I wouldn't even do that: any value in the
HTTP response that is accessible via JavaScript - basically any header
value or response body value - could be sniffed and intercepted via
MITM XSS attacks.
It's better to store the JWT value in a secure-only, http-only cookie
(cookie config: setSecure(true), setHttpOnly(true)). This guarantees
that the browser will:
only ever transmit the cookie over a TLS connection and,
never make the cookie value available to JavaScript code.
This approach is almost everything you need to do for best-practices
security. The last thing is to ensure that you have CSRF protection on
every HTTP request to ensure that external domains initiating requests
to your site cannot function.
The easiest way to do this is to set a secure only (but NOT http only)
cookie with a random value, e.g. a UUID.
I don't understand why we need the cookie with the random value to ensure that external domains initiating requests to your site cannot function. This doesn't come free with Same-origin policy?
From OWASP:
Checking The Origin Header
The Origin HTTP Header standard was introduced as a method of
defending against CSRF and other Cross-Domain attacks. Unlike the
referer, the origin will be present in HTTP request that originates
from an HTTPS url.
If the origin header is present, then it should be checked for
consistency.
I know that the general recommendation from OWASP itself is Synchronizer Token Pattern but I can't see what are the vulnerabilities that remains in:
TLS + JWT in secure httpOnly cookie + Same-origin policy + No XSS vulnerabilities.
UPDATE 1:
The same-origin policy only applies to XMLHTTPRequest, so a evil site can make a form POST request easily an this will break my security. An explicit origin header check is needed. The equation would be:
TLS + JWT in secure httpOnly cookie + Origin Header check + No XSS vulnerabilities.
Summary
I had a misunderstood concepts about Same-origin policy and CORS that #Bergi, #Neil McGuigan and #SilverlightFox helped me to clarify.
First of all, what #Bergi says about
SOP does not prevent sending requests. It does prevent a page from
accessing results of cross-domain requests.
is an important concept. I thought that a browser doesn't make the request to the cross domain accordingly to the SOP restriction but this is only true for what Monsur Hossain calls a "not-so-simple requests" in this excellent tutorial.
Cross-origin requests come in two flavors:
simple requests
"not-so-simple requests" (a term I just made up)
Simple requests are requests that meet the following criteria:
HTTP Method matches (case-sensitive) one of:
HEAD
GET
POST
HTTP Headers matches (case-insensitive):
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type, but only if the value is one of:
application/x-www-form-urlencoded
multipart/form-data
text/plain
So, a POST with Content Type application/x-www-form-urlencoded will hit to the server (this means a CSRF vulnerability) but the browser will not make accessible the results from that request.
A POST with Content Type application/json is a "not-so-simple request" so the browser will make a prefligth request like this
OPTIONS /endpoint HTTP/1.1
Host: https://server.com
Connection: keep-alive
Access-Control-Request-Method: POST
Origin: https://evilsite.com
Access-Control-Request-Headers: content-type
Accept: */*
Accept-Encoding: gzip, deflate, sdch
Accept-Language: es-ES,es;q=0.8
If the server respond with for example:
Access-Control-Allow-Origin: http://trustedsite.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: content-type
Content-Type: text/html; charset=utf-8
the browser will not make the request at all, because
XMLHttpRequest cannot load http://server.com/endpoint. Response to
preflight request doesn't pass access control check: The
'Access-Control-Allow-Origin' header contains the invalid value
'trustedsite.com'. Origin 'evilsite.com' is therefore not allowed access.
So I think that Neil was talking about this when he pointed out that:
the Same-origin Policy only applies to reading data and not
writing it.
However, with the origin header explicit control that I proposed to Bergi I think is enough with respect to this issue.
With respect to my answer to Neil I didn't mean that that answer was the one to all my question but it remembered me another important issue about SOP and it was that the policy only applies to XMLHTTPRequest's.
In conclusion, I think that the equation
TLS + JWT in secure httpOnly cookie + Origin Header check + No XSS vulnerabilities.
is a good alternative if the API is in another domain like SilverlightFox says. If the client is in the same domain that the client I will have troubles with requests that doesn't include the Origin header. Again from the cors tutorial:
The presence of the Origin header does not necessarily mean that the
request is a cross-origin request. While all cross-origin requests
will contain an Origin header, some same-origin requests might have
one as well. For example, Firefox doesn't include an Origin header on
same-origin requests. But Chrome and Safari include an Origin header
on same-origin POST/PUT/DELETE requests (same-origin GET requests will
not have an Origin header).
Silverlight pointed this out to.
The only risk that remains is that a client can spoof the origin header to match the allowed origin, so the answer i was looking for was actually this
UPDATE: for those who watch this post, I have doubts about if the origin header is needed at all using JWT.
The equation would be:
TLS + JWT stored in secure cookie + JWT in request header + No XSS vulnerabilities.
Also, the previous equation has httpOnly cookie but this won't work if you got the client and the server in different domains (like many SPA application today) because the cookie wouldn't be sent with each request to the server. So you need access the JWT token stored in the cookie and send it in a header.
Why Same-origin policy isn't enough to prevent CSRF attacks?
Because the Same-origin Policy only applies to reading data and not writing it.
You want to avoid http://compromised.com from making a request like this (from the user's browser):
POST https://example.com/transfer-funds
fromAccountId:1
toAccountId:666
A legit request would look like this:
POST https://example.com/transfer-funds
fromAccountId: 1
toAccountId: 666
csrfToken: 249f3c20-649b-44de-9866-4ed72170d985
You do this by demanding a value (the CSRF token) that cannot be read by an external site, ie in an HTML form value or response header.
Regarding the Origin header, older browsers don't support it, and Flash had some vulnerabilities that let the client change it. Basically you'd be trusting Adobe not to screw anything up in the future...does that sound like a good idea?
ensure that you have CSRF protection on every HTTP request
You only need CSRF protection on requests with side-effects, such as changing state or sending a message
I just want to summarize the answers.
As other mentioned SOP applies only to XmlHttpRequests. This means by specification browsers must send ORIGIN header along with requests that were made by means of XmlHttpRequests.
If you check Chromium sends origin when you submit form as well. However this doesn't mean other browsers do. The image below illustrates two post requests made in Firefox. One is made by submitting a form and a second one using XHR. Both requests were made from http://hack:3002/changePassword to http://bank:3001/chanePassword.
Browser is allowed to not send the origin header if request was made from the same domain. So server should check the origin policy only when origin header is set.
The conclusion is: if you use cookies and a request comes to server without origin header, you can't differentiate whether it was made by submitting a form from another domain or by XHR within the same domain. That's why you need additional check with CSRF.
TLDR:
As long as the request is sent(with cookie), there is a possibility of an csrf attack.
SOP(Same-origin-Policy) only don't allow cross-origin reads(except for embedded element such as <script> <img> etc), but allow cross-origin writes.
More specifically, browser use CORS mechanism to get Cross-Origin resource, there is two situations:
Simple requests
Browser will add the Origin field, then send to the server.(csrf happen)
Others (Other than Simple requests)
CORS preflight was triggered, the request may not be sent to the server.(csrf may happen)
Reference
https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
I have a HTTP "rest" web service API that I am creating for accessing user data.
The web service integrates with a XACML policy decision point. The policy decision point determines if the request is authorized as a function of the user and the data being accessed. Generally, the response is "permit" (action is allowed) or "deny" (action is not allowed).
If permit, then the call continues and data is returned.
If deny, then the call is aborted and an HTTP 403 (forbidden) is returned.
However, certain policies have "obligations" to indicate that the action is conditionally allowed with further action. The analogy that I generally use is the "this credit card transaction may be allowed, but the clerk needs to view the customer's ID, and then make this call again asserting that the ID matches the credit card".
In my web service, I want to prompt the client to take action and include additional information in the request URL to indicate that the obligation was met. I am communicating this information with structured body response understood by the client application.
My question is what is the appropriate HTTP status code to use in the scenario.
"403" would not be appropriate (text from HTTP spec Authorization will not help and the request SHOULD NOT be repeated).
My best guess would "401" (unauthorized), but I am not sure if this status code is specifically around the use of the authorization header and username/password type of concerns.
Maybe you could use the HTTP status code 303 or 307 to point the user to a temporary redirect location that somehow encodes the extra "obligation"?
A 401 response seems reasonable to me. The HTTP authentication mechanism is extensible, so you could conceivably create a custom authentication scheme so that you are properly setting the WWW-Authenticate header as required by the RFC. Browsers won't know what to do with your custom scheme, but I assume your clients aren't browsers anyway, if they have to understand what an obligation is.
Example request sequence:
PUT /some/resource/that/has/obligations HTTP/1.1
Content-Type: application/json
Authorization: token my-oauth-token
HTTP/1.1 401 Unauthorized
WWW-Authenticate: obligation urn:my-app:my-obligation;param1;param2
PUT /some/resource/that/has/obligations HTTP/1.1
Content-Type: application/json
Authorization: token my-oauth-token
Authorization: obligation urn:my-app:my-obligation:result=ok
HTTP/1.1 201 Created
Location: /some/resource/that/has/obligations/1
Another option would be to return a 202 on the initial post or put, and then confirm it on a later post. This option would require a little more state management server side, since you'd first accept the operation, and then wait for the client to confirm it.
PUT /some/resource/that/has/obligations HTTP/1.1
Content-Type: application/json
Authorization: token my-oauth-token
HTTP/1.1 202 Accepted
Location: /some/resource/that/has/obligations/1
X-Obligation: urn:my-app:my-obligation;param1;param2
POST /some/resource/that/has/obligations/1 HTTP/1.1
Content-Type: application/json
Authorization: token my-oauth-token
X-Obligation: urn:my-app:my-obligation;result=ok
HTTP/1.1 200 OK
One thing to always keep in mind with obligations, though... they're always enforced client-side, so unless the obligation involves hitting another service that your service can double-check against, you never know if the client actually performed the obligation. If you don't control the client, then obligations are really just theatre.
I am trying to make a GetOrders call on eBay Trading API, and am getting CORS error:
XMLHttpRequest cannot load https://api.ebay.com/ws/api.dll.
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin '' is therefore not allowed
access.
These are the headers :
Request URL:https://api.ebay.com/ws/api.dll, Request Method:OPTIONS
Status Code:200 OK Request Headersview source Accept:*/*
Accept-Encoding:gzip,deflate,sdch Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:x-ebay-api-siteid, x-ebay-api-dev-name, content-type, x-ebay-api-cert-name, accept, x-ebay-api-request-encoding, x-ebay-api-call-name, x-ebay-api-app-name, x-ebay-api-compatibility-level
Access-Control-Request-Method:POST Connection:keep-alive
Host:api.ebay.com Origin:<mydomain.com>
Referer:<mydomain.com> User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36
Response Headers:
Allow:GET, HEAD, POST, TRACE, OPTIONS
Connection:keep-alive
Content-Length:0
Date:Sun, 19 Jan 2014 18:15:10 GMT
Server:Apache-Coyote/1.1
X-EBAY-ESB-GUID:urn:uuid:UUIDDDODSDFLSDKLSDKFJKS
X-EBAY-ESB-SITEID:0
Does eBay need to explicitly "allow" my domain for CORS ? I have searched internet, and eBay API documentation, and there is no indication of this. If I use curl, the API call is successfully run, though the eBay trading API does not return Access-Control-Allow-Credentials: true.
In practice, all browsers enforce the Cross-Origin Resource Sharing restrictions described in the CORS standard which includes sending the preflight OPTIONS request like you're seeing. The check is not mandatory, though and non-browser user agents such as curl, Android/iOS HTTP clients, etc. generally do not enforce this check.
There is no eBay developer process for allowing particular domains to make cross-domain resource requests. The CORS support is somewhat patchy across the different APIs. For example, the finding API call findItemsByKeywords works fine cross-domain (see here), while other services don't allow it (see a very old but still unanswered request here).
This is not a great answer, but you can always use a backend proxy to make your calls. In this way, your backend can make the calls with curl or the like, and you also get the benefit of being able to hide your appID, user tokens and other secrets from local inspection of your javascript.
You may also want to file a request with eBay, but some of those tickets have been languishing for some time.