Prevent "clickjacking" aka "UI redress attack" through AWS security web services - amazon-web-services

It is possible to prevent Clickjacking or "UI redress attack" through any of AWS security services like WAF or CloudFront?
https://www.owasp.org/index.php/Clickjacking
It is known that certain security HTTP headers can be added to user requests that would instruct browsers to enforce certain security measures as follows,
Strict Transport Security
Content-Security-Policy
X-Content-Type-Options
X-Frame-Options
X-XSS-Protection
Referrer-Policy
These can be configured at the back end code level, however, I would like to know if one desire not to set these parameters at the application level, can this be done at the AWS level using any of their security gateway services like WAF or CloudFront?

You can take some actions at the Server level by adding headers in response mentioned in below link:
Clickjacking Defense Cheat Sheet
Secure Apache from Clickjacking with X-FRAME-OPTIONS

EDIT: This has a similar answer here:
For those that come along now, you can use Lambda#Edge to add HSTS headers as well as other "frame-buster" headers like x-frame-options and referrer-policy.
This is quite cheap, working out to about 30 cents per million requests.
This link from the AWS networking and content delivery blog describes how to do this in detail.
It is too long to repeat the entire contents here but essentially it describes the following process flow:
Here is how the process works:
Viewer navigates to website.
Before CloudFront serves content from the cache it will trigger any Lambda function associated with the Viewer Request trigger for that behavior.
CloudFront serves content from the cache if available, otherwise it goes to step 4.
Only after CloudFront cache ‘Miss’, Origin Request trigger is fired for that behavior.
S3 Origin returns content.
After content is returned from S3 but before being cached in CloudFront, Origin Response trigger is fired.
After content is cached in CloudFront, Viewer Response trigger is fired and is the final step before viewer receives content.
Viewer receives content.
Once again, in case the blog linked to disappears, the below code is a sample to add security headers via Lambda (remembering this is to be run by CloudFront using Lambda#Edge integration):
'use strict';
exports.handler = (event, context, callback) => {
//Get contents of response
const response = event.Records[0].cf.response;
const headers = response.headers;
//Set new headers
headers['strict-transport-security'] = [{key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubdomains; preload'}];
headers['content-security-policy'] = [{key: 'Content-Security-Policy', value: "default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'"}];
headers['x-content-type-options'] = [{key: 'X-Content-Type-Options', value: 'nosniff'}];
headers['x-frame-options'] = [{key: 'X-Frame-Options', value: 'DENY'}];
headers['x-xss-protection'] = [{key: 'X-XSS-Protection', value: '1; mode=block'}];
headers['referrer-policy'] = [{key: 'Referrer-Policy', value: 'same-origin'}];
//Return modified response
callback(null, response);
};

Related

SPA post requests in cloudfront

I have created a cloudfront distribution with two origins (s3 bucket and aws apigateway). The s3 bucket serves my Elm spa successfully but when I try to send a post request via the SPA to my api gateway I get a 403. This is my elm post request:
testReq : Cmd Msg
testReq =
request
{ method = "POST"
, headers = []
, url = "https://xxxxxxxxxx.cloudfront.net/api/testy/gettalent"
, body = jsonBody testBody
, expect = Http.expectWhatever Resp
, timeout = Nothing
, tracker = Nothing
}
Usually, HTTP 403 means access denied, In your case, I believe that your website domain and the API domains are different. Therefore this could be access denied due to "Cross-Origin Request Blocked" error.
if that's the case, you should enable CORS on your API resource method.
Reference:
https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html

Can I use AWS API Gateway as a reverse proxy for a S3 website?

I have a serverless website on AWS S3. But S3 have a limitation that I want to overcome: it don't allow me to have friendly URLs.
For example, I would like to replace URL:
www.mywebsite.com/user.html?login=daniel
With this URL friendly:
www.mywebsite.com/user/daniel
So, I would like to know if I can use Lambda together with API Gateway to achieve this.
My idea is:
API Gateway ---> Lambda function ---> fetch S3 resource
The API Gateway will get ANY request, and pass information to a Lambda funcion, that will process some logic using the request URL (including maybe some database query) and then fetch the resource from S3.
I know AWS API Gateway main purpose is to be a gateway to REST APIs, but can we also use it as a proxy to an entire website?
The good option can be to use CloudFront as a reverse proxy, you can use Viewer/Origin response request to trigger lambda and fetch the resource from S3.
https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-examples.html
https://aws.amazon.com/blogs/networking-and-content-delivery/amazon-s3-amazon-cloudfront-a-match-made-in-the-cloud/
It is possible to use API Gateway as a reverse proxy for a S3 website.
I was able to do that following steps below:
In AWS API Gateway, create a "proxy resource" with resource path = "{proxy+}"
Go to AWS Certificate Manager and request a wildcard certificate for your website (*.mywebsite.com)
AWS will tell you to create a CNAME record in you domain registrar, to verify that you own that domain
After your certificate is validated, go to AWS API Gateway and create a Custom Domain Name (click on "Custom Domain Names" and then "Create Custom Domain Name"). In "domain name" type your domain (www.mywebsite.com) and select the ACM Certificate that you just created (step 1 above). Create a "Base Path Mapping" with path = "/" and in "destination" select your API and stage.
After that, you will need to add another CNAME record, with the CloudFront "Target Domain Name" that was generated for that Custom Domain Name.
In the Lambda, we can route the requests:
'use strict';
const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const myBucket = 'myBucket';
exports.handler = async (event) => {
var responseBody = "";
if (event.path=="/") {
responseBody = "<h1>My Landing Page</h1>";
responseBody += "<a href='/xpto'>link to another page</a>";
return buildResponse(200, responseBody);
}
if (event.path=="/xpto") {
responseBody = "<h1>Another Page</h1>";
responseBody += "<a href='/'>home</a>";
return buildResponse(200, responseBody);
}
if (event.path=="/my-s3-resource") {
var params = {
Bucket: myBucket,
Key: 'path/to/my-s3-resource.html',
};
const data = await s3.getObject(params).promise();
return buildResponse(200, data.Body.toString('utf-8'));
}
return buildResponse(404, '404 Error');
};
function buildResponse(statusCode, responseBody) {
var response = {
"isBase64Encoded": false,
"statusCode": statusCode,
"headers": {
"Content-Type" : "text/html; charset=utf-8"
},
"body": responseBody,
};
return response;
}
A good bet would be to use CloudFront and Lambda#Edge.
Lambda#Edge allows you to run Lambda function in the edge location of the CloudFront CDN network.
CloudFront gives you the option to hook into various events during its lifecycle and apply logic.
This article looks like it might be describing something similar to what you're talking about.
https://aws.amazon.com/blogs/networking-and-content-delivery/implementing-default-directory-indexes-in-amazon-s3-backed-amazon-cloudfront-origins-using-lambdaedge/

Google Cloud Storage: CORS settings doesn't work for signed URLs

The response of PUT request with signed URL doesn't contain header Access-Control-Allow-Origin.
import os
from datetime import timedelta
import requests
from google.cloud import storage
os.environ['GOOGLE_APPLICATION_CREDENTIALS'] = <path to google credentials>
client = storage.Client()
bucket = client.get_bucket('my_bucket')
policies = [
{
'origin': ['*'],
'method': ['PUT'],
}
]
bucket.cors = policies
bucket.update()
blob = bucket.blob('new_file')
url = blob.generate_signed_url(timedelta(days=30), method='PUT')
response = requests.put(url, data='some data')
for header in response.headers.keys():
print(header)
Output:
X-GUploader-UploadID
ETag
x-goog-generation
x-goog-metageneration
x-goog-hash
x-goog-stored-content-length
x-goog-stored-content-encoding
Vary
Content-Length
Date
Server
Content-Type
Alt-Svc
As you can see there is no CORS-headers. So, can I conclude that GCS doesn't support CORS properly/fully?
Cross Origin Resource Sharing (CORS) allows interactions between resources from different origins. By default, in Google Cloud Storage it is prohibited/disabled in order to prevent malicious behavior.
You can enable it either using Cloud Libraries, Rest API or Cloud SDK, keeping in mind following rules:
Authenticate using user/service account with the permissions for Cloud Storage type: FULL_CONTROL.
Using XML API to get proper CORS headers, use one of the two URLs:
- storage.googleapis.com/[BUCKET_NAME]
- [BUCKET_NAME].storage.googleapis.com
Origin storage.cloud.google.com/[BUCKET_NAME] will not respond with CORS header.
Request need proper ORIGIN header to match bucket policy ORIGIN configuration as stated in the point 3 of the CORS troubleshooting documentation, in case of your code:
headers = {
'ORIGIN': '*'
}
response = requests.put(url, data='some data', headers=headers)
for header in response.headers.keys():
print(header)
gives following output:
X-GUploader-UploadID
ETag
x-goog-generation
x-goog-metageneration
x-goog-hash
x-goog-stored-content-length
x-goog-stored-content-encoding
Access-Control-Allow-Origin
Access-Control-Expose-Headers
Content-Length
Date
Server
Content-Type
I had this issue. For me the problem was I was using POST instead of PUT. Furthermore, I had to set the Content-Type of the upload to match the content type used to generate the form. The default Content-Type in the demo is "application/octet-stream", so I had to change it to be whatever was the content type of the upload. When doing the XMLHttpRequest, I just had to send the file directly instead of using FormData.
This was how I got the signed url.
const options = {
version: 'v4',
action: 'write',
expires: Date.now() + 15 * 60 * 1000, // 15 minutes
contentType: 'application/octet-stream',
};
// Get a v4 signed URL for uploading file
const [url] = await storage
.bucket("lsa-storage")
.file(upload.id)
.getSignedUrl(options as any);

How to restrict Cloudfront access to my domain only?

i need to find a solution how to do it. Basically i have one .m3u8 video and i want to restrict it to be only played on my domain. Basically what are people doing right now, is stealing my video and playing on their sites, which causes big overload and a lot of bandwidth...
d23ek3kf.cloudfront.net/video.m3u8 > mydomain.com > video accessable
d23ek3kf.cloudfront.net/video.m3u8 > randomdomain.com > video not accessable
This solution does not prevent anyone from downloading your content and the uploading it to their own site, but it does prevent other sites from hot-linking to your content.
Create a Lambda#Edge Viewer Request trigger. This allows you to inspect the request before the cache is checked, and either allow processing to continue or to return a generated response.
'use strict';
exports.handler = (event, context, callback) => {
// extract the request object
const request = event.Records[0].cf.request;
// extract the HTTP `Referer` header if present
// otherwise an empty string to simplify the matching logic
const referer = (request.headers['referer'] || [ { value: '' } ])[0].value;
// verify that the referring page is yours
// replace example.com with your domain
// add other conditions with logical or ||
if(referer.startsWith('https://example.com/') ||
referer.startsWith('http://example.com/'))
{
// return control to CloudFront and allow the request to continue normally
return callback(null,request);
}
// if we get here, the referring page is not yours.
// generate a 403 Forbidden response
// you can customize the body, but the size is limited to ~40 KB
return callback(null, {
status: '403',
body: 'Access denied.',
headers: {
'cache-control': [{ key: 'Cache-Control', value: 'private, no-cache, no-store, max-age=0' }],
'content-type': [{ key: 'Content-Type', value: 'text/plain' }],
}
});
};
The way to do it is using signed URLs. Your website will generate signed URLs for the video that is being played that the user wants to play and cloudfront will allow the content to be downloaded. Signed URLs exire after a specified amount of time.
Any other website will just have he link of the video which is not enough to download the video. Take a look at AWS documentation here to understand the details and mechanism to achieve it. https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html

Cross-Domain Cookies

I have two webapps WebApp1 and WebApp2 in two different domains.
I am setting a cookie in WebApp1 in the HttpResponse.
How to read the same cookie from HttpRequest in WebApp2?
I know it sounds weird because cookies are specific to a given domain, and we can't access them from different domains; I've however heard of CROSS-DOMAIN cookies which can be shared across multiple webapps. How to implement this requirement using CROSS-DOMAIN cookies?
Note: I am trying this with J2EE webapps
Yes, it is absolutely possible to get the cookie from domain1.example by domain2.example. I had the same problem for a social plugin of my social network, and after a day of research I found the solution.
First, on the server side you need to have the following headers:
header("Access-Control-Allow-Origin: http://origin.domain:port");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Allow-Methods: GET, POST");
header("Access-Control-Allow-Headers: Content-Type, *");
Within the PHP-file you can use $_COOKIE[name]
Second, on the client side:
Within your AJAX request you need to include 2 parameters
crossDomain: true
xhrFields: { withCredentials: true }
Example:
type: "get",
url: link,
crossDomain: true,
dataType: 'json',
xhrFields: {
withCredentials: true
}
As other people say, you cannot share cookies, but you could do something like this:
centralize all cookies in a single domain, let's say cookiemaker.example
when the user makes a request to example.com you redirect him to cookiemaker.example
cookiemaker.example redirects him back to example.com with the information you need
Of course, it's not completely secure, and you have to create some kind of internal protocol between your apps to do that.
Lastly, it would be very annoying for the user if you do something like that in every request, but not if it's just the first.
But I think there is no other way.
As far as I know, cookies are limited by the "same origin" policy. However, with CORS you can receive and use the "Server B" cookies to establish a persistent session from "Server A" on "Server B".
Although, this requires some headers on "Server B":
Access-Control-Allow-Origin: http://server-a.example.com
Access-Control-Allow-Credentials: true
And you will need to send the flag "withCredentials" on all the "Server A" requests (ex: xhr.withCredentials = true;)
You can read about it here:
http://www.html5rocks.com/en/tutorials/cors/
https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS
There's no such thing as cross domain cookies. You could share a cookie between foo.example.com and bar.example.com but never between example.com and example2.com and that's for security reasons.
The smartest solution is to follow facebook's path on this. How does facebook know who you are when you visit any domain? It's actually very simple:
The Like button actually allows Facebook to track all visitors of the external site, no matter if they click it or not. Facebook can do that because they use an iframe to display the button. An iframe is something like an embedded browser window within a page. The difference between using an iframe and a simple image for the button is that the iframe contains a complete web page – from Facebook. There is not much going on on this page, except for the button and the information about how many people have liked the current page.
So when you see a like button on cnn.com, you are actually visiting a Facebook page at the same time. That allows Facebook to read a cookie on your computer, which it has created the last time you’ve logged in to Facebook.
A fundamental security rule in every browser is that only the website that has created a cookie can read it later on. And that is the advantage of the iframe: it allows Facebook to read your Facebook-cookie even when you are visiting a different website. That’s how they recognize you on cnn.com and display your friends there.
Source:
http://dorianroy.com/blog/2010/04/how-facebooks-like-button-works/
https://stackoverflow.com/a/8256920/715483
Cross-domain cookies are not allowed (i.e. site A cannot set a cookie on site B).
But once a cookie is set by site A, you can send that cookie even in requests from site B to site A (i.e. cross-domain requests):
XMLHttpRequest from a different domain cannot set cookie values for their own domain unless withCredentials is set to true before making the request. The third-party cookies obtained by setting withCredentials to true will still honor same-origin policy and hence can not be accessed by the requesting script through document.cookie or from response headers.
Make sure to do these things:
When setting the cookie in a response
The Set-Cookie response header includes SameSite=None if the requests are cross-site (note a request from www.example.dev to static.example.dev is actually a same-site request, and can use SameSite=Strict)
The Set-Cookie response header should include the Secure attribute if served over HTTPS; as seen here and here
When sending/receiving the cookie:
The request is made with withCredentials: true, as mentioned in other answers here and here, including the original request whose response sets the cookie set in the first place
For the fetch API, this attribute is credentials: 'include', vs withCredentials: true
For jQuery's ajax method, note you may need to supply argument crossDomain: true
The server response includes cross-origin headers like Access-Control-Allow-Origin, Access-Control-Allow-Credentials, Access-Control-Allow-Headers, and Access-Control-Allow-Methods
As #nabrown points out: "Note that the "Access-Control-Allow-Origin" cannot be the wildcard (*) value if you use the withCredentials: true" (see #nabrown's comment which explains one workaround for this.
In general:
Your browser hasn't disabled 3rd-party cookies. (* see below)
Things that you don't need (just use the above):
domain attribute in the Set-Cookie; you can choose a root domain (i.e. a.example.com can set a cookie with a domain value of example.com, but it's not necessary; the cookie will still be sent to a.example.com, even if sent from b.other-site.example
For the cookie to be visible in Chrome Dev Tools, "Application" tab; if the value of cookie HttpOnly attribute is true, Chrome won't show you the cookie value in the Application tab (it should show the cookie value when set in the initial request, and sent in subsequent responses where withCredentials: true)
Notice the difference between "path" and "site" for Cookie purposes. "path" is not security-related; "site" is security-related:
path
Servers can set a Path attribute in the Set-Cookie, but it doesn't seem security related:
Note that path was intended for performance, not security. Web pages having the same origin still can access cookie via document.cookie even though the paths are mismatched.
site
The SameSite attribute, according to example.dev article, can restrict or allow cross-site cookies; but what is a "site"?
It's helpful to understand exactly what 'site' means here. The site is the combination of the domain suffix and the part of the domain just before it. For example, the www.example.dev domain is part of the example.dev site...
This means a request to static.example.dev from www.example.dev, is a sameSite request (the only difference in the URLs is in the subdomains).
The public suffix list defines this, so
it's not just top-level domains like .com but also includes services
like github.io
This means a request to your-project.github.io from my-project.github.io, is a a cross-site request (these URLs are at different domains, because github.io is the domain suffix; the domains your-project vs my-project are different; hence different sites)
This means what's to the left of the public suffix; is the subdomain (but the subdomain is a part of the host; see the BONUS reply in this answer)
www is the subdomain in www.example.dev; same site as static.example.dev
your-project is the domain in your-project.github.io; separate site as my-project.github.io
In this URL https://www.example.com:8888/examples/index.html, remember these parts:
the "protocol": https://
the "scheme": https
the "port": 8888
the "domain name" aka location.hostname: www.example.com
the "domain suffix" aka "top-level domain" (TLD): com
the "domain": example
the "subdomain": www (the subdomain could be single-level (like www) or multi-level (like foo.bar in foo.bar.example.com)
the "site" (as in "cross-site" if another URL had a different "site" value): example.com
"site" = "domain" + "domain suffix" = example.com
the "path": /examples/index.html
Useful links:
Anatomy of a URL
Same-Origin cookie policy and URL anatomy
SameSite cookies explained
Secure cross-domain cookies for HTTP | Journal of Internet Services and Applications | Full Text
draft-ietf-httpbis-rfc6265bis-03
Web Security 1: Same-Origin and Cookie Policy
Set-Cookie - HTTP | MDN
(Be careful; I was testing my feature in Chrome Incognito tab; according to my chrome://settings/cookies; my settings were "Block third party cookies in Incognito", so I can't test Cross-site cookies in Incognito.)
You cannot share cookies across domains. You can however allow all subdomains to have access. To allow all subdomains of example.com to have access, set the domain to .example.com.
It's not possible giving other.example access to example.com's cookies though.
Do what Google is doing. Create a PHP file that sets the cookie on all 3 domains. Then on the domain where the theme is going to set, create a HTML file that would load the PHP file that sets cookie on the other 2 domains. Example:
<html>
<head></head>
<body>
<p>Please wait.....</p>
<img src="http://domain2.example/setcookie.php?theme=whateveryourthemehere" />
<img src="http://domain3.example/setcookie.php?theme=whateveryourthemehere" />
</body>
</html>
Then add an onload callback on body tag. The document will only load when the images completely load that is when cookies are set on the other 2 domains. Onload Callback:
<head>
<script>
function loadComplete(){
window.location="http://domain1.example";//URL of domain1
}
</script>
</head>
<body onload="loadComplete()">
setcookie.php
We set the cookies on the other domains using a PHP file like this:
<?php
if(isset($_GET['theme'])){
setcookie("theme", $_GET['theme'], time()+3600);
}
?>
Now cookies are set on the three domains.
You can attempt to push the cookie val to another domain using an image tag.
Your mileage may vary when trying to do this because some browsers require you to have a proper P3P Policy on the WebApp2 domain or the browser will reject the cookie.
If you look at plus.google.com p3p policy you will see that their policy is:
CP="This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info."
that is the policy they use for their +1 buttons to these cross domain requests.
Another warning is that if you are on https make sure that the image tag is pointing to an https address also otherwise the cookies will not set.
There's a decent overview of how Facebook does it here on nfriedly.com
There's also Browser Fingerprinting, which is not the same as a cookie, but serves a like purpose in that it helps you identify a user with a fair degree of certainty. There's a post here on Stack Overflow that references upon one method of fingerprinting
I've created an NPM module, which allows you to share locally-stored data across domains:
https://www.npmjs.com/package/cookie-toss
By using an iframe hosted on Domain A, you can store all of your user data on Domain A, and reference that data by posting requests to the Domain A iframe.
Thus, Domains B, C, etc. can inject the iframe and post requests to it to store and access the desired data. Domain A becomes the hub for all shared data.
With a domain whitelist inside of Domain A, you can ensure only your dependent sites can access the data on Domain A.
The trick is to have the code inside of the iframe on Domain A which is able to recognize which data is being requested. The README in the above NPM module goes more in depth into the procedure.
Hope this helps!
Since it is difficult to do 3rd party cookies and also some browsers won't allow that.
You can try storing them in HTML5 local storage and then sending them with every request from your front end app.
One can use invisible iframes to get the cookies. Let's say there are two domains, a.example and b.example. For the index.html of domain a.example one can add (notice height=0 width=0):
<iframe height="0" id="iframe" src="http://b.example" width="0"></iframe>
That way your website will get b.example cookies assuming that http://b.example sets the cookies.
The next thing would be manipulating the site inside the iframe through JavaScript. The operations inside iframe may become a challenge if one doesn't own the second domain. But in case of having access to both domains referring the right web page at the src of iframe should give the cookies one would like to get.
Along with #Ludovic(approved answer) answers we need to check one more option when getting set-cookies header,
set-cookie: SESSIONID=60B2E91C53B976B444144063; Path=/dev/api/abc; HttpOnly
Check for Path attribute value also. This should be the same as your API starting context path like below
https://www.example.com/dev/api/abc/v1/users/123
or use below value when not sure about context path
Path=/;
function GetOrder(status, filter) {
var isValid = true; //isValidGuid(customerId);
if (isValid) {
var refundhtmlstr = '';
//varsURL = ApiPath + '/api/Orders/Customer/' + customerId + '?status=' + status + '&filter=' + filter;
varsURL = ApiPath + '/api/Orders/Customer?status=' + status + '&filter=' + filter;
$.ajax({
type: "GET",
//url: ApiPath + '/api/Orders/Customer/' + customerId + '?status=' + status + '&filter=' + filter,
url: ApiPath + '/api/Orders/Customer?status=' + status + '&filter=' + filter,
dataType: "json",
crossDomain: true,
xhrFields: {
withCredentials: true
},
success: function (data) {
var htmlStr = '';
if (data == null || data.Count === 0) {
htmlStr = '<div class="card"><div class="card-header">Bu kriterlere uygun sipariş bulunamadı.</div></div>';
}
else {
$('#ReturnPolicyBtnUrl').attr('href', data.ReturnPolicyBtnUrl);
var groupedData = data.OrderDto.sort(function (x, y) {
return new Date(y.OrderDate) - new Date(x.OrderDate);
});
groupedData = _.groupBy(data.OrderDto, function (d) { return toMonthStr(d.OrderDate) });
localStorage['orderData'] = JSON.stringify(data.OrderDto);
$.each(groupedData, function (key, val) {
var sortedData = groupedData[key].sort(function (x, y) {
return new Date(y.OrderDate) - new Date(x.OrderDate);
});
htmlStr += '<div class="card-header">' + key + '</div>';
$.each(sortedData, function (keyitem, valitem) {
//Date Convertions
if (valitem.StatusDesc != null) {
valitem.StatusDesc = valitem.StatusDesc;
}
var date = valitem.OrderDate;
date = date.substring(0, 10).split('-');
date = date[2] + '.' + date[1] + '.' + date[0];
htmlStr += '<div class="col-lg-12 col-md-12 col-xs-12 col-sm-12 card-item clearfix ">' +
//'<div class="card-item-head"><span class="order-head">Sipariş No: <a href="ViewOrderDetails.html?CustomerId=' + customerId + '&OrderNo=' + valitem.OrderNumber + '" >' + valitem.OrderNumber + '</a></span><span class="order-date">' + date + '</span></div>' +
'<div class="card-item-head"><span class="order-head">Sipariş No: <a href="ViewOrderDetails.html?OrderNo=' + valitem.OrderNumber + '" >' + valitem.OrderNumber + '</a></span><span class="order-date">' + date + '</span></div>' +
'<div class="card-item-head-desc">' + valitem.StatusDesc + '</div>' +
'<div class="card-item-body">' +
'<div class="slider responsive">';
var i = 0;
$.each(valitem.ItemList, function (keylineitem, vallineitem) {
var imageUrl = vallineitem.ProductImageUrl.replace('{size}', 200);
htmlStr += '<div><img src="' + imageUrl + '" alt="' + vallineitem.ProductName + '"><span class="img-desc">' + ProductNameStr(vallineitem.ProductName) + '</span></div>';
i++;
});
htmlStr += '</div>' +
'</div>' +
'</div>';
});
});
$.each(data.OrderDto, function (key, value) {
if (value.IsSAPMigrationflag === true) {
refundhtmlstr = '<div class="notify-reason"><span class="note"><B>Notification : </B> Geçmiş siparişleriniz yükleniyor. Lütfen kısa bir süre sonra tekrar kontrol ediniz. Teşekkürler. </span></div>';
}
});
}
$('#orders').html(htmlStr);
$("#notification").html(refundhtmlstr);
ApplySlide();
},
error: function () {
console.log("System Failure");
}
});
}
}
Web.config
Include UI origin and set Allow Credentials to true
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="http://burada.com" />
<add name="Access-Control-Allow-Headers" value="Content-Type" />
<add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" />
<add name="Access-Control-Allow-Credentials" value="true" />
</customHeaders>
</httpProtocol>
Three main kinds of browser-based storage:
session storage
local storage
cookie storage
Secure cookies - are used by encrypted websites to offer protection from any possible threats from a hacker.
access cookie - document.cookie. This means that this cookie is exposed and can be exploited through cross-site scripting. The saved cookie values can be seen through the browser console.
As a precaution, you should always try to make your cookies inaccessible on the client-side using JavaScript.
HTTPonly - ensures that a cookie is not accessible using the JavaScript code. This is the most crucial form of protection against cross-scripting attacks.
A secure attribute - ensures that the browser will reject cookies unless the connection happens over HTTPS.
sameSite attribute improves cookie security and avoids privacy leaks.
sameSite=Lax - It is set to Lax (sameSite = Lax) meaning a cookie is only set when the domain in the URL of the browser matches the domain of the cookie, thus eliminating third party’s domains. This will restrict cross-site sharing even between different domains that the same publisher owns. we need to include SameSite=None to avoid the new default of Lax:
Note: There is a draft spec that requires that the Secure attribute be set to true when the SameSite attribute has been set to 'none'. Some web browsers or other clients may be adopting this specification.
Using includes as { withCredentials: true } must include all the cookies with the request from the front end.
const data = { email: 'youremailaddress#gmail.com' , password: '1234' };
const response = await axios.post('www.yourapi.com/login', data , { withCredentials: true });
Cookie should only be accepted over a secure HTTPS connection. In order to get this to work, we must move the web application to HTTPS.
In express.js
res.cookie('token', token, {
maxAge: 1000 * 60 * 60 * 24, // would expire after (for 15 minutes 1000 * 60 * 15 ) 15 minutes
httpOnly: true, // The cookie only accessible by the web server
sameSite: 'none',
secure: true, // Marks the cookie to be used with HTTPS only.
});
Reference 1, Reference 2
Read Cookie in Web Api
var cookie = actionContext.Request.Headers.GetCookies("newhbsslv1");
Logger.Log("Cookie " + cookie, LoggerLevel.Info);
Logger.Log("Cookie count " + cookie.Count, LoggerLevel.Info);
if (cookie != null && cookie.Count > 0)
{
Logger.Log("Befor For " , LoggerLevel.Info);
foreach (var perCookie in cookie[0].Cookies)
{
Logger.Log("perCookie " + perCookie, LoggerLevel.Info);
if (perCookie.Name == "newhbsslv1")
{
strToken = perCookie.Value;
}
}
}