Configure URL Rewrite using server variable to support multiple origins - url-rewriting

I was dealing with CORS issue a couple days ago where I need to support multiple origins. I did some research and found a couple posts pointing me to this great tool (URL Rewrite). I got the solution I wanted following Paco Zarate tip here: Access-control-allow-origin with multiple domains using URL Rewrite. I also found another post here: http://www.carlosag.net/articles/enable-cors-access-control-allow-origin.cshtml showing me how to achieve the same solution with a different method of configuration within URL Rewrite.
Paco Zarate solution works like a charm. Why am I still asking question? It's just for learning purposes. And also I think the second post configuration yields a more elegant list of origins/domains I want to white-listed. For ease of maintainability I can just go to the Rewrite Maps and see all of my AllowedOrigins.
When attempted the second post's solution, I got this message: ERR_CONNECTION_RESET under Console tab of the browser debugger tool. And the request header under Network tab says "Provisional headers are shown"
Many thanks in advance.
My config file:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<httpErrors errorMode="Detailed" />
<urlCompression doStaticCompression="true" doDynamicCompression="true" />
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Headers" value="Origin, Content-Type, Accept" />
<add name="Access-Control-Request-Method" value="POST" />
<add name="Access-Control-Allow-Credentials" value="true" />
<add name="Access-Control-Allow-Origin" value="http://localhost:8080" />
</customHeaders>
</httpProtocol>
<rewrite>
<rules>
<rule name="Capture Origin Header">
<match url=".*" />
<conditions>
<add input="{HTTP_ORIGIN}" pattern=".+" />
</conditions>
<serverVariables>
<set name="CAPTURED_ORIGIN" value="{C:0}" />
</serverVariables>
<action type="None" />
</rule>
<rule name="Preflight Options" enabled="false" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{REQUEST_METHOD}" pattern="^OPTIONS$" />
</conditions>
<action type="CustomResponse" statusCode="200" statusReason="Preflight" statusDescription="Preflight" />
</rule>
</rules>
<rewriteMaps>
<rewriteMap name="AllowedOrigins">
<add key="http://somedomain:8080" value="http://localhost:8080" />
</rewriteMap>
</rewriteMaps>
<outboundRules>
<rule name="Set-Access-Control-Allow-Origin for known origins">
<match serverVariable="RESPONSE_Access-Control-Allow-Origin" pattern=".+" negate="true" />
<conditions>
<add input="{AllowedOrigins:{CAPTURED_ORIGIN}}" pattern=".+" />
</conditions>
<action type="Rewrite" value="{C:0}" />
</rule>
</outboundRules>
</rewrite>
<tracing>
<traceFailedRequests>
<add path="*">
<traceAreas>
<add provider="WWW Server" areas="Rewrite" verbosity="Verbose" />
</traceAreas>
<failureDefinitions timeTaken="00:00:00" statusCodes="500" verbosity="Error" />
</add>
</traceFailedRequests>
</tracing>
</system.webServer>

I had a lot of issues trying to use URL Rewrite module to enable CORS, after a lot of troubleshooting I found that for IIS 7.5+ you can use IIS CORS Module: https://www.iis.net/downloads/microsoft/iis-cors-module
Your web.config should be something like this:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<cors enabled="true" failUnlistedOrigins="true">
<add origin="http://localhost:8080" allowCredentials="true">
<allowMethods>
<add method="POST" />
</allowMethods>
</add>
</cors>
</system.webServer>
</configuration>
You can find the configuration reference in here: https://learn.microsoft.com/en-us/iis/extensions/cors-module/cors-module-configuration-reference

Related

Getting a URL Rewrite error 500 when prerendering

Maybe someone can help me out.
I have two rules in my web.config, one for pre-render and one for Vue.
I have stacked them like this:
<rules>
<rule name="prerender.io" stopProcessing="true">
<match url="(\.js|\.json|\.css|\.xml|\.less|\.png|\.jpg|\.jpeg|\.gif|\.pdf|\.doc|\.txt|\.ico|\.rss|\.zip|\.mp3|\.rar|\.exe|\.wmv|\.doc|\.avi|\.ppt|\.mpg|\.mpeg|\.tif|\.wav|\.mov|\.psd|\.ai|\.xls|\.mp4|\.m4a|\.swf|\.dat|\.dmg|\.iso|\.flv|\.m4v|\.torrent|\.ttf|\.woff|\.svg)" negate="true" />
<conditions logicalGrouping="MatchAny">
<add input="{HTTP_USER_AGENT}" pattern="googlebot|bingbot|yandex|baiduspider|facebookexternalhit|twitterbot|rogerbot|linkedinbot|embedly|quora\ link\ preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp" />
<add input="{QUERY_STRING}" pattern="_escaped_fragment_" />
</conditions>
<serverVariables>
<set name="HTTP_X_PRERENDER_TOKEN" value="<removed>" />
</serverVariables>
<action type="Rewrite" url="https://service.prerender.io/https://{HTTP_HOST}{REQUEST_URI}" appendQueryString="false" logRewrittenUrl="true" />
</rule>
<rule name="Vue" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_URI}" pattern="^/api/.*" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
The problem is, when googlebot tries to access my site, most of the time it comes back with a URL Rewrite error 500. If I remove googlebot from the prerender rule, it will stop throwing the rewrite error, which leads me to believe the issue is with that rule, but their support team tell me it's not.
Can anyone see any glaringly obvious issues with my rules? Or know something I am missing?
It turns out this was due to ARR proxy not being turned on.
Because I host on Azure, it had to create a new file called applicationHost.xdt and add this:
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<system.webServer>
<proxy xdt:Transform="InsertIfMissing" enabled="true" preserveHostHeader="false"
reverseRewriteHostInResponseHeaders="false" />
<rewrite>
<allowedServerVariables>
<add name="HTTP_X_PRERENDER_TOKEN" xdt:Transform="InsertIfMissing" />
</allowedServerVariables>
</rewrite>
</system.webServer>
</configuration>
If you use IIS, you can follow this link:
https://learn.microsoft.com/en-us/iis/extensions/configuring-application-request-routing-arr/creating-a-forward-proxy-using-application-request-routing

Why does the name of my Publish Profile affect the transformation of my Web.config?

I think I have encountered a strange interaction where the name of my Publish Profile is affecting the contents of the published Web.config.
For context, I have 2 solution configurations; Release & Staging, and a folder Publish Profile called 'Release.pubxml'. Below are stripped down versions of the three web configs involved.
Web.config:
<configuration>
<connectionStrings>
<add name="DBEntities" connectionString="{Web.config connection string}" />
</connectionStrings>
<appSettings>
<add key="WEBSITEURL" value="http://website.com" />
<add key="ADMINURL" value="http://admin.website.com" />
</appSettings>
</configuration>
Web.Release.config:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<connectionStrings>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(name)" name="DBEntities" connectionString="{Web.Release.config connection string}" />
</connectionStrings>
<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
</system.web>
<system.webServer>
<rewrite xdt:Transform="Insert">
<rules>
<rule name="HTTPtoHTTPSredirect" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" redirectType="Found" url="https://{HTTP_HOST}/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Web.Staging.config:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<connectionStrings>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(name)" name="DBEntities" connectionString="{Web.Staging.config connection string}" />
</connectionStrings>
<appSettings>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(key)" key="WEBSITEURL" value="http://stagingwebsite.com" />
<add xdt:Transform="SetAttributes" xdt:Locator="Match(key)" key="ADMINURL" value="http://admin.stagingwebsite.com />
</appSettings>
<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
</system.web>
</configuration>
I had recently adjusted our Staging web config, and tried publishing under the Release.pubxml profile, but with the Configuration set to Staging, rather than Release. The result I expected was the original Web.config file, with all Web.Staging.config xdt transformations applied.
What ended up happening was a Frankenstein mix of the Web.Staging.config and the Web.Release.config transforming into Web.config:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<connectionStrings>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(name)" name="DBEntities" connectionString="{Web.Release.config connection string}" />
</connectionStrings>
<appSettings>
<add xdt:Transform="SetAttributes" xdt:Locator="Match(key)" key="WEBSITEURL" value="http://stagingwebsite.com" />
<add xdt:Transform="SetAttributes" xdt:Locator="Match(key)" key="ADMINURL" value="http://admin.stagingwebsite.com />
</appSettings>
<system.web>
<compilation xdt:Transform="RemoveAttributes(debug)" />
</system.web>
<system.webServer>
<rewrite xdt:Transform="Insert">
<rules>
<rule name="HTTPtoHTTPSredirect" stopProcessing="true">
<match url="(.*)" />
<conditions>
<add input="{HTTPS}" pattern="off" ignoreCase="true" />
</conditions>
<action type="Redirect" redirectType="Found" url="https://{HTTP_HOST}/{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
You'll notice it has the Release connection string, the Staging WEBSITEURL/ADMINURL, and the Release HTTP rules.
I could not find any trace of the Release configuration anywhere in my settings. I had checked the configuration manager and all projects were set to build as Staging & I had cleaned/rebuilt multiple times. I had also tested it with both VS 2022 & VS 2019, and with multiple, separate solutions; each yielded the same result.
What ended up "fixing" it, was changing the name from 'Release.pubxml' to anything but Release.pubxml or Staging.pubxml. Although you wouldn't usually want to name your publish profile anything outside of what its configured to, why does the name of the publish profile override the configuration build you have set? With some further investigation, it seems that it might be transforming the chosen configured build first (Staging), then it transforms any config with the same name as your publish profile (Release).
This interaction seems extremely dangerous to me, so would anybody be able to explain to me why Visual Studio would do this (or if I have maybe encountered a bug)?

IIS ARR Reverse Proxy Disk Cache

I want to setup disk cache for reverse proxy responses for forwarded requests. I expect all requests to http://localhost:88/ to be forwarded to https://stackoverflow.com/ (as an example) with following rewrite rule:
<rule name="ReverseProxy1" stopProcessing="true">
<match url="(.*)" />
<action type="Rewrite" url="https://stackoverflow.com/{R:1}" />
</rule>
Which works perfectly fine.
And I want all responses from https://stackoverflow.com/ to be cached on disk. I have following setup in applicationHost.config:
<diskCache scavengerInterval="00:05:00">
<driveLocation path="C:\inetpub\temp\cache" maxUsage="0" />
<compression enabled="true">
<add mimeType="text/*" enabled="true" />
<add mimeType="message/*" enabled="true" />
<add mimeType="application/x-javascript" enabled="true" />
<add mimeType="application/javascript" enabled="true" />
</compression>
<sharedDriveLocation path="" />
</diskCache>
<proxy enabled="true" httpVersion="PassThrough" reverseRewriteHostInResponseHeaders="true">
<cache requestConsolidationEnabled="true" queryStringHandling="Accept" validationInterval="00:01:00" />
</proxy>
<rewrite>
<globalRules>
<rule name="ARR_CacheControl_b17f5877-33f6-4bed-be49-f3c07a38cfef" enabled="true" patternSyntax="Wildcard">
<match url="*" />
<serverVariables>
<set name="ARR_CACHE_CONTROL_OVERRIDE" value="1,max-age=1800" />
</serverVariables>
<conditions>
<add input="{HTTP_HOST}" pattern="stackoverflow.com" />
</conditions>
</rule>
</globalRules>
</rewrite>
Unfortunately disk cache is never hit. I can tell it by examining IIS Log with X-ARR-CACHE-HIT=0 entries. And cache folder is always empty. The folder was created by IIS manager UI and I provided access rights to Application Pool identity to this folder, so I assume that the problem is not in access rights to cache folder.
Did I miss anything? Looking for a solution in internet didn't give me any results so any input is very appreciated.
I found out that the problem was that the Vary response header is present in SO response. And based on the reply on IIS forums ARR doesn't support caching when there is a Vary header in response.

403 error when going to Drupal site's /admin page

I have a frustrating issue here. Not really sure what is causing it. My Drupal site is running on a Windows server with IIS 7. When I go to the http://example.com/admin page I get a 403 error. However, if I login with http://example.com/?q=user then go to /admin it works fine. I'm guessing there's something wrong with a setting in IIS or there's some kind of permission that is wrong. There is no admin directory in my site's root directory, so its not a case of a rogue folder screwing things up. Any ideas??
Here is my web.config..
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<!-- Don't show directory listings for URLs which map to a directory. -->
<directoryBrowse enabled="false" />
<rewrite>
<rules>
<rule name="Protect files and directories from prying eyes" stopProcessing="true">
<match url="\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$" />
<action type="CustomResponse" statusCode="403" subStatusCode="0" statusReason="Forbidden" statusDescription="Access is forbidden." />
</rule>
<rule name="Force simple error message for requests for non-existent favicon.ico" stopProcessing="true">
<match url="favicon\.ico" />
<action type="CustomResponse" statusCode="404" subStatusCode="1" statusReason="File Not Found" statusDescription="The requested file favicon.ico was not found" />
</rule>
<!-- Rewrite URLs of the form 'x' to the form 'index.php?q=x'. -->
<rule name="Short URLs" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
<add input="{URL}" pattern="^/favicon.ico$" ignoreCase="false" negate="true" />
<!--<add input="{REQUEST_URI}" pattern="^/(gallery2|message-boards)" negate="true" />-->
</conditions>
<action type="Rewrite" url="index.php?q={R:1}" appendQueryString="true" />
</rule>
</rules>
</rewrite>
<!-- <httpErrors>
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath="" path="/index.php" responseMode="ExecuteURL" />
</httpErrors>
-->
<defaultDocument>
<!-- Set the default document -->
<files>
<remove value="index.php" />
<add value="index.php" />
</files>
</defaultDocument>
<staticContent>
<mimeMap fileExtension=".woff" mimeType="application/x-woff" />
</staticContent>
</system.webServer>
</configuration>
/?q=user means you don't have clean URLs enabled.
Try logging in by navigating to /?q=admin. Then make sure you have .htaccess in your site's root directory (it's hidden so you'll have to enable "see hidden files" in whatever platform you're using).
Once you're sure it's there - if not redownload Drupal with hidden files visible to get the file - clear the cache, go to "Configuration" -> "Search and metadata" -> "Clean URLs" -> enable

URL Rewrite - Redirect to different port and changing URL using map

I want to rewrite URL to redirect to different port, based on HTTP_URL while preserving rest of URL and query string (if specified).
For example,
http://host/john/page.aspx should be redirected to http://host:1900/page.aspx,
http://host/paul/anotherpage.aspx?query to http://host:1901/anotherpage.aspx?query
and http://host/ringo to http://host:1902/
I've added bunch of rules for every allowed port, but it does not look efficient or manageable.
I'm trying to employ map, (ie john->1900, paul->1901) but cannot figure out how to assemble desired URL.
Any suggestions?
It took some fiddling to get it working but looking back at it the solutions is quite simple and elegant.
<rewrite>
<rules>
<clear />
<rule name="Redirect known names to ports" stopProcessing="true">
<match url=".*" />
<conditions trackAllCaptures="true">
<add input="{REQUEST_URI}" pattern="/(.*?)/(.*)" />
<add input="{NameToPort:{C:1}}" pattern="(.+)" />
</conditions>
<action type="Redirect" url="http://{HTTP_HOST}:{C:3}/{C:2}" appendQueryString="false" redirectType="Permanent" />
</rule>
</rules>
<rewriteMaps>
<rewriteMap name="NameToPort">
<add key="john" value="1900" />
<add key="paul" value="1901" />
<add key="ringo" value="1902" />
</rewriteMap>
</rewriteMaps>
</rewrite>
Let me know if this is what you were looking for.

Resources