Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cross origin http request CORS fails with response header missing 'Access-Control-Allow-Credentials: true' #620

Closed
satjinder opened this issue Aug 30, 2016 · 44 comments
Labels
Milestone

Comments

@satjinder
Copy link

satjinder commented Aug 30, 2016

Cross origin http request (CORS) to Azure function does not return 'Access-Control-Allow-Credentials:true'. Is there a way to add custom headers?

Details

  • It is a nodeJS function
  • Only one CORS entry: 'http://localhost'
  • Tried with both AJAX and Javascript API
  • Authentication with google OAUTH

Error:
Response to preflight request doesn't pass access control check: Credentials flag is 'true', but the 'Access-Control-Allow-Credentials' header is ''. It must be 'true' to allow credentials. Origin 'http://localhost' is therefore not allowed access.

Mostly followed as per the following post except mine is CORS:
https://shellmonger.com/2016/02/12/using-azure-app-service-authentication-with-a-web-application/

Code

    let options = {
        mode :'cors',  //tried both with and without
        method: 'GET',
        credentials: 'include',
        cache: 'no-cache'
    };
    fetch('https://<function-name>.azurewebsites.net/.auth/me.', options).then((response) => {
        this.logger.debug('[checkauth-callback-1]: Response = ', response);
        if (!response.ok && response.status !== 401)
            throw new Error('Invalid Response from Config Endpoint', response);
        if (response.status === 401)
            return false;
        return response.json();
    })

**also tried:**
 headers:new Headers({
        'content-type': 'application/json',
        'Accept': 'application/json',
        'Access-Control-Allow-Credentials':true,
        'Access-Control-Allow-Origin':true
'''


A similar issue raised on stackoverflow:
http://stackoverflow.com/questions/39215513/how-to-add-customer-http-header-in-response-from-azure-function
@satjinder satjinder changed the title Cross origin http request CORS fails with response header missing ''Access-Control-Allow-Credentials: true' Cross origin http request CORS fails with response header missing 'Access-Control-Allow-Credentials: true' Aug 30, 2016
@christopheranderson
Copy link
Contributor

Have you tried enabling CORS via the Function App Settings?

@satjinder
Copy link
Author

@christopheranderson thanks for the reply.
Yes, that is the only way I know. Under allowed origins, 'http://localhost' is the only entry I have got.

@satjinder
Copy link
Author

Another stackoverflow issue but for azure app services. But in this case developer got away by disabling azure CORS handling and handled it in the web api code.

http://stackoverflow.com/questions/36860423/enable-access-control-allow-credentials-header-in-azure-website-azure-app-servi

But in the case of azure function, with http trigger your function do not get chance to have say in that.

So we either need a way for azure to handle iit correctly or a way to bypass and handle in the code.

Please help me.

@satjinder
Copy link
Author

I have finally managed to get around the issue. The trick is to remove all the CORS entries from Azure Functions app and handle it directly in your code. Thanks to the tip shared in post regarding azure app service.

module.exports = function(context, req) { context.log('Node.js HTTP trigger function processed a request. RequestUri=%s', req.originalUrl); f (req.query.name || (req.body && req.body.name)) { context.res = { // status: 200, /* Defaults to 200 */ body: {name: (req.query.name || req.body.name) } }; } else { context.res = { status: 400, body: "Please pass a name on the query string or in the request body" }; } context.res.headers = { 'Access-Control-Allow-Credentials' : 'true', 'Access-Control-Allow-Origin' : 'http://localhost', 'Access-Control-Allow-Origins' : 'http://localhost', 'Content-Type': 'application/json' }; context.done(); };

And from client side javascript:

` var request = new Request(url, {
method: 'GET',
credentials: 'include',
cache: 'no-cache',
mode:'cors'
});

fetch(request)
.then(function(response) {
console.log(response);
response.json().then(function(data){
console.log(data);
alert(data)
});
})
.catch(function(err){
console.log(err);
}) ;`

I close this issue for now, but it will be great if we can specify additional headers at the application level.

@satjinder satjinder reopened this Sep 1, 2016
@satjinder
Copy link
Author

Though with the workaround, I am able to get resources, but it still does not allow /.auth/* calls.
Ideally I would like to make the call /.auth/me call and establish if the user is authenticated as described in the example:

https://shellmonger.com/2016/02/12/using-azure-app-service-authentication-with-a-web-application/

@lindydonna
Copy link
Contributor

This is an Azure App Service feature request, not specific to Azure Functions. We'll put this on the backlog category for tracking purposes, but please file a UserVoice suggestion for Azure Web Apps here: https://feedback.azure.com/forums/169385-web-apps-formerly-websites

@lindydonna lindydonna added this to the backlog milestone Sep 12, 2016
@ricklove
Copy link

ricklove commented Jan 21, 2017

@satjinder Thanks for the tip that removing all CORS entries allows for the headers to be set manually in the response in code.

That solved my problem, and I can have my own custom logic for checking valid domains now.

Note: Now I get a warning that CORS is not configured for the functions domain:

    ERROR: CORS is not configured for this function app. Please add https://functions.azure.com to your CORS list.

Is there any problem with ignoring this?

@securityvoid
Copy link

@ricklove Can you please clarify what you did?

This is my code:

module.exports = function(context, req){
    var shared = require('../auth-shared-libraries');
    var cors_url = "https://mywebsite.azurewebsites.net"
    context.res =  {
        status: 200,
        headers: {
            "Access-Control-Allow-Credentials" : "true",
            "Access-Control-Allow-Origin" : cors_url,
            "Content-Type" : "application/json"
        },
        body : { "status" : "alive"}
    }
    context.done();
}

When I clear all URLS from API -> CORS in the Azure Portal the "Access-Control-Allow-Credentials" header works properly and is set to true, but "Access-Control-Allow-Origin" is not passed through and therefore is not set.

When I set API -> CORS in the Azure Portal to my domain name, that header is set properly, but Access-Control-Allow-Credentials is not set.

@ricklove
Copy link

@securityvoid

That's not the behavior I obtained when I removed all entries in the CORS settings (in the Functions area). Note: There is no wildcard entry and I am getting an error in the portal that says, "CORS is not configured for this function app. "

Both my headers are getting through fine:

// (Excuse the custom code in Typescript)
let wildcard = true;

export function addCorsHeaders<T extends { [name: string]: string }>(request: T.SimpleRequest, headers: T): T {
    // 'Access-Control-Allow-Origin': '*',
    //    'Access-Control-Allow-Credentials': 'true',

    let origin = request.headers['origin'] || request.headers['Origin'];

    if (wildcard || cors_domains.indexOf(origin) >= 0) {
        // Here is the important part
        headers['Access-Control-Allow-Origin'] = origin;
        headers['Access-Control-Allow-Credentials'] = 'true';
    }

    return headers;
}
// This is where I call that
context.done(null, {
        headers: addCorsHeaders(request,{
            'Content-Type': 'application/json',
            'Set-Cookie': `blobBaseName=${blobBaseName}; Expires=${new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toUTCString()}; Secure; HttpOnly`
        }),
        body: {
            ok: true,
            data: { urls, isNew },
        }
    });

And here are my response headers in Chrome:

Access-Control-Allow-Credentials:true
Access-Control-Allow-Origin:https://my-site-not-azure.toldpro.com
Cache-Control:no-cache
Content-Encoding:gzip
Content-Length:488
Content-Type:application/json

After looking through the code, I see a difference in our responses:

  • I used the context.done method
  • You used context.res =

Perhaps the context.res is not respecting your header and the done method is.

@securityvoid
Copy link

@ricklove Thank-you. Implementing your code helped me isolate the issue.

Apparently because I cleared out the "deployments" directory of logs, it actually caused my future deployments to say they were working, but actually fail to put my code into wwwroot. (I'm continuously deploying based on a git repository). As a result when I thought I was putting other code out to add new headers... I actually wasn't.

Once I changed out my code to mimic your code it became absolutely clear that my changes weren't doing anything, and I explored further to find the real issue. I'm still trying to get the code deployed correctly, but I'm pretty sure that was the real reason why I had the results.

@ricklove
Copy link

A heads up:

The workaround to remove all CORS in the portal no longer appeared to work.

I discovered that this was because I had enabled the new Azure Functions Proxies (preview).

Upon disabling proxies, it worked again.

So, if you are having trouble getting manual CORS to work:

  • remove all CORS entries in the portal
  • make sure Proxies is disabled

@ahmelsayed
Copy link
Contributor

@safihamid not sure if this is expected side effect of enabling proxies.

@safihamid
Copy link
Contributor

safihamid commented Mar 28, 2017

@ricklove we don't really do anything specific with CORS in Functions Proxies. Would you please let us know your scenario and how we can repro this?

This is our default CORS setting for Proxies:

<cors> <allowed-origins> <origin>*</origin> </allowed-origins> <allowed-methods> <method>*</method> </allowed-methods> <allowed-headers> <header>*</header> </allowed-headers> </cors>

@ricklove
Copy link

@safihamid Yes, of course, I was using proxies so it was unfortunate that I had to disable them because I could find no workaround for the CORS problem.

Setup

Remove all entries from the portal CORS panel. (Including the * wildcard entry.) This is required in order to bypass the CORS logic as mentioned above.

Code

In a Node.JS http function:

    context.done(null, {
        status: 200,
        headers: {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin' : origin,
            'Access-Control-Allow-Credentials'], 'true'
        },
        body: {
            ok: true
        },
    });

Repro Problem

You will need to call this function with an ajax cross origin call (from a different domain) in order to trigger the CORS browser behavior.

  • If proxies is disabled, the above function will respond with the correct Access-Control headers.
  • If proxies is enabled, the above function will respond with 'Access-Control-Allow-Origin' = '*' (but wildcard origin is not allowed for 'Access-Control-Allow-Credentials' == 'true' , i.e. ajax - withCredentials).

@safihamid
Copy link
Contributor

@ricklove thanks for the repro, we have a fix for Proxy for this and will release it in the next few days.
I will notify this thread when the fix is live.

@ricklove
Copy link

@safihamid Wow that was fast.

Tell the team thanks for your work!

@safihamid
Copy link
Contributor

fyi the fix is live now! You will need to update your Proxy runtime version to ~0.2 from the portal.

@lindydonna
Copy link
Contributor

Thanks @safihamid. I'm closing this issue as it seems like the main ask has been handled. If there are other feature request, please open a new issue so we can track properly.

@ahmelsayed
Copy link
Contributor

I'm not sure the issue @safihamid fixed is the same one that was originally reported. The proxies issue was more of a tangent that just happen to get reported here.

@lindydonna
Copy link
Contributor

Good catch @ahmelsayed. I'm still not sure about the original issue, there was a lot of back-and-forth. Can the original posters please comment?

@circuitrider
Copy link

I hope this gets fixed soon. Getting bit bad by it :\

@burma-shave
Copy link

I posted this issue in the UserVoice as @lindydonna suggested:
https://feedback.azure.com/forums/169385-web-apps/suggestions/32371078-access-control-allow-credentials-not-set-in-creden

Quite new CORS stuff so I hope I described the problem accurately. I'd encourage anyone interested in getting this fixed to upvote on UserVoice

@burma-shave
Copy link

The feature request was declined because the feature is not supported?
https://feedback.azure.com/forums/169385-web-apps/suggestions/32371078-access-control-allow-credentials-not-set-in-creden

@nevercast
Copy link

I feel like the request has been misunderstood and needs to be reconsidered. This workaround isn't a solution, we need either a way to disable Azure's CORS responses and remove the warning regarding functions.azure.com, or the Azure CORS support needs to be extended to support the -Credentials header. Citing an article by a Microsoft developer for a workaround isn't a closing statement for declining. If anything it highlights that the issue is known and needs to be fixed.

I understand that this is a Web App issue and not a Functions issue, however, I hope the developers for Azure Functions can help us get this resolved.

Kind regards,
Josh.

@securityvoid
Copy link

@nevercast I agree completely. The whole response on that thread for the NFR doesn't make any sense at all and this is very much needed. There has to be a disconnect somewhere. Adding a configuration option in the portal that sets another HTTP Header does not sound like something that should be a huge development effort. The note on that thread is signed by an "Oded", could it possibly be @odvoskin ?

@odvoskin
Copy link

odvoskin commented May 7, 2018

Adding @cgillum as he may have some additional details here after it was discussed internally.

@cgillum
Copy link
Member

cgillum commented May 8, 2018

Sorry for being late to the party. I agree, it looks like the feature request on the feedback site was misunderstood.

We discussed this a bit internally and there's no technical reason why we can't add support for Access-Control-Allow-Credentials. There were some concerns about the security implications of supporting such a feature, and we're discussing that internally now. One reason why we didn't expect this to be a problem is that we expected most SPA apps to use authentication tokens instead of cookies to authenticate with the backend, thus removing the need for Access-Control-Allow-Credentials.

Let me know if you have any other thoughts/comments/feedback you'd be willing to share about your need for this. I'll reply back after another round of internal discussion.

@burma-shave
Copy link

One reason why we didn't expect this to be a problem is that we expected most SPA apps to use authentication tokens instead of cookies to authenticate with the backend, thus removing the need for Access-Control-Allow-Credentials

I may well be wrong about this, but I was under the impression you still need Access-Control-Allow-Credentials to be true to pass auth tokens in the Authorization header. It's not just cookies. When I looked at this originally I was trying to allow an SPA to make a cross domain request to an API using a JWT bearer token in the Authorization header.

@nevercast
Copy link

@burma-shave You're not wrong at all, that is precisely my use-case also, a JWT using Authorization: Bearer {}, Access-Control-Allow-Credentials is required for this.

@cgillum
Copy link
Member

cgillum commented May 8, 2018

@burma-shave Thanks for that info, a quick search confirms: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

The Access-Control-Allow-Credentials response header indicates whether or not the response to the request can be exposed to the page. It can be exposed when the true value is returned.

Credentials are cookies, authorization headers or TLS client certificates.

We'll look into adding support for this. Thanks for your patience on this issue. @odvoskin can we update the feedback response? I think the case has been made that this feature is needed.

@odvoskin
Copy link

odvoskin commented May 8, 2018

Done on my end. @cgillum - please leave comments in the UserVoice item about approach and any timing we can share, when that becomes available.

@nevercast
Copy link

Thank you for reconsidering! This is great news. 👍

@satjinder
Copy link
Author

satjinder commented May 8, 2018

I am so glad to hear that. I did give up on this.

@securityvoid
Copy link

There were some concerns about the security implications of supporting such a feature, and we're discussing that internally now. One reason why we didn't expect this to be a problem is that we expected most SPA apps to use authentication tokens instead of cookies

I just wanted to talk a little bit about the "security implications" of cookies vs. tokens. There seems to be this impression that tokens are somehow more secure than cookies. I disagree with this. Both have their advantages and disadvantages and I think cookies, when handled properly, come out slightly ahead. I want the "Access-Control-Allow-Credentials" header because we use cookies, and we are a security consulting company. This doesn't mean we're right, but I've thought a decent bit about this.

From a security stand-point, utilizing tokens completely prevents Cross-Site Request Forgery (CSRF) attacks. This is the main, and only real security advantage I can see that tokens have over cookies. They work to prevent CSRF attacks because a CSRF vulnerability is reliant on the web-browser automatically adding the session token when a request is sent for a given domain, even from an untrusted domain. Since tokens have to be added by JavaScript code running in the context of the domain, CSRF is stopped by default.

However, being immune to this problem comes at a cost. Since JavaScript has to be able to access and send the session token, this means that Cross-Site Scripting (XSS) (Should the vulnerability exist) will ALWAYS be able to access the session token. While XSS's possibilities of actually being able to execute are reduced with a JSON application that properly sets the Content-Type to application/json; XSS is still one of the most common vulnerabilities in web applications. With tokens you are guaranteed that the worst possible exploit of XSS is available, the stealing of the session token. This is a huge negative, that I believe completely counters the positive of stopping CSRF. There is no way to use a token, and avoid this exploit scenario IF XSS is found in the application.

Cookies on the other hand are vulnerable by default to CSRF since any web-browser will automatically add the cookie to a request destined for a given domain. You can prevent this behavior, however, by sending CSRF tokens from the framework itself to the server. This will mean you are sending both a session cookie and a CSRF token; but when you do you have completely blocked CSRF.

In addition, with cookies you have the option of setting the "httpOnly" flag on cookie creation. This flag makes it impossible for JavaScript to read the cookie value, even though that value is still sent to the server for authentication. This means that even if a XSS vulnerability is discovered, it will NOT be possible to take advantage of the worst exploit for that vulnerability; stealing the session token. The attacker still will be able to utilize the XSS to grab the CSRF token, and send a fraudulent request to the server on behalf of the user; but this would also be possible with token auth.

As a result if you use cookies, there are settings and ways to mitigate the additional risks posed with using that choice. If you use tokens, you do not have that option. That's just my 2 cents on this topic :-).

If there are security advantages of tokens I'm missing; please let me know. I'm definitely open to learning something new.

In any case, thank-you for re-opening the new feature request to get this into the product! It is very much appreciated!

@nevercast
Copy link

Thanks for the interesting write up, @securityvoid, and perhaps this isn't the place to continue a discussion on this, but if an XSS is found in your web app, then hijacking the Fetch/XML Request API used by the app and sending requests is still an equal threat, cookie or token, if you have an XSS vulnerability you should consider the entire account compromised on that domain.

Sure, if you use an httpOnly cookie, the cookie cannot be stolen and sent to a third party domain for abuse, but the abuse can take place directly in the victim's browser. I think this highlights that token v. cookie, XSS is a severe threat for either, though I think XSS is a far better-understood problem compared to CSRF for example.

In my use case, I'm using the Authorization header which also requires the -Credentials CORS rule, with a token. I could choose to store my JWT token in an httpOnly cookie, and while this means I cannot read it from my App, I still get some of the benefits of both.

There is a lot of things to balance here, the argument isn't perfectly simple (for example, a non-httpOnly cookie is likely less secure than a token in localStorage). Developers can make mistakes about security, and they do quite often. But I don't think these concerns are a for or against justification of supporting -Credentials CORS on Azure. I don't believe it is the responsibility of Azure App Service/Function Apps to try and sandbox a developer and in doing so breaking perfectly secure means of client-server authorization (when done correctly).

Lastly, I think it is important to say, that I am in no way a security professional. My work is in web development, so from a security point of view, someone should do their own research on top of my comments.

@clarkio
Copy link

clarkio commented Sep 6, 2018

I'm with @securityvoid on the discussion of cookie vs. token. I do feel you have a point @nevercast, however, I'm not sure XSS is better understood (though I could very well be wrong). I would argue more that XSS is more difficult to mitigate than CSRF especially with the implementation of the SameSite cookie attribute and therefore choose cookies as the transport mechanism for such data.

In any case, I think both transport mechanisms should be supported by Azure Functions/App Service but I'm just running into this now and not sure where things stand internally.

Update: Here's the exact error thrown by my browser even though the response is received in the "Network"

The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. Origin '< removed >' is therefore not allowed access.

@pauldalyii
Copy link

I've been affected by this too.

I went down the path of removing all the CORS settings from the portal in order to use the CORS nuget packages in my service so that I could support .AllowCredentials(); as well as .SetIsOriginAllowedToAllowWildcardSubdomains();.

This works well except that the .auth/* routes are not impacted by the custom CORS logic in my service.

If the CORS settings in the portal supported wildcard subdomains as well as allow-credentials, I'd remove the custom logic from my API and let its routes as well as the .auth/* routes obtain their CORS settings from the portal configuration.

@pauldalyii
Copy link

@cgillum, @odvoskin... Thanks for reconsidering this issue.

Are you able to share an update? Curious to know if you have an idea on timing.

Thanks again!

@cgillum
Copy link
Member

cgillum commented Dec 11, 2018

Yes, sorry for the delay! We recently added support for Access-Control-Allow-Credentials. See our announcement here: https://azure.microsoft.com/en-us/blog/simplifying-security-for-serverless-and-web-apps-with-azure-functions-and-app-service/.

Here is the note from the documentation:

If your app requires credentials such as cookies or authentication tokens to be sent, the browser may require the ACCESS-CONTROL-ALLOW-CREDENTIALS header on the response. To enable this in App Service, set properties.cors.supportCredentials to true in your CORS config. This cannot be enabled when allowedOrigins includes '*'.

@cgillum cgillum closed this as completed Dec 11, 2018
@pauldalyii
Copy link

Thanks for the update @cgillum! That's awesome.

I understand reasoning behind not allowing allowedOrigins to be '*' in conjunction with supportCredentials.

Would you consider allowing supportCredentials to work in conjunction with .SetIsOriginAllowedToAllowWildcardSubdomains();?

This would still limit the scope of where the credentials could be shared, but enable multi-tenant service scenarios.

Thanks!

@TechInceptions
Copy link

Yes, sorry for the delay! We recently added support for Access-Control-Allow-Credentials. See our announcement here: https://azure.microsoft.com/en-us/blog/simplifying-security-for-serverless-and-web-apps-with-azure-functions-and-app-service/.

Here is the note from the documentation:

If your app requires credentials such as cookies or authentication tokens to be sent, the browser may require the ACCESS-CONTROL-ALLOW-CREDENTIALS header on the response. To enable this in App Service, set properties.cors.supportCredentials to true in your CORS config. This cannot be enabled when allowedOrigins includes '*'.

"To enable this in App Service, set properties.cors.supportCredentials to true in your CORS config"
What does this refer to?
In which tool/service/SDK/package do we find "properties.cors.supportCredentials"?
In which tool/service/SDK/package do we find "CORS config"?

What are you assuming that everybody is using?

@cgillum
Copy link
Member

cgillum commented Jan 7, 2019

@pauldalyii would you mind opening a separate issue for your request? That will allow us to better track it.

@TechInceptions this is the name of an ARM (Azure Resource Manager) property. See this tutorial for how to configure CORS in Azure App Service (works exactly the same for Functions): https://docs.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-rest-api#enable-cors

Example CLI command:

az resource update \
    --name web \
    --resource-group myResourceGroup \
    --namespace Microsoft.Web \
    --resource-type config \
    --parent sites/<app_name> \
    --set properties.cors.allowedOrigins="['http://localhost:5000']" \
    --set properties.cors.supportCredentials=true

This can also be done via the Azure Resource Explorer web interface.

maiqbal11 pushed a commit that referenced this issue Nov 1, 2019
Closes #617
Closes #275
Closes #180
Closes #620
Closes #351
Closes #237
Closes #408
Closes #402
Closes #325
Closes #308
Closes #297
Closes #296
@ghost ghost locked as resolved and limited conversation to collaborators Jan 2, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests