Querystring issue with .Net Core url rewrite - asp.net-core

Working on an app in ASP.NET Core I am having a problem trying to rewrite a single url like:
https://localhost:44318/Details?content=My-url-to-rewrite&id=221
into
https://localhost:44318/mypage
The code I am using at the beginning of the Configure() method into Startup.cs is the following:
app.UseRewriter(new RewriteOptions()
.AddRewrite(#"^Details?content=My-url-to-rewrite&id=221", "/mypage", skipRemainingRules: true));
The strange thing is that if I try to rewrite the url without the querystring like the following, it works
app.UseRewriter(new RewriteOptions()
.AddRewrite(#"^Details", "/mypage", skipRemainingRules: true));
And even adding the question mark to append the querystring it works, like the following
app.UseRewriter(new RewriteOptions()
.AddRewrite(#"^Details?", "/mypage", skipRemainingRules: true));
But as soon as I add even a single character after the question mark the url is not rewritten and the page gets linked as usually without any error.
Any idea?
Thanks in advance.

You can implement your own ReWrite Middleware :
Configure :
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
....
var options = new RewriteOptions()
.Add(RedirectMyRequests);
app.UseRewriter(options);
...
}
static void RedirectMyRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
// Because we're redirecting back to the same app, stop processing if the request has already been redirected
//If the modified path is similar with previous, add if statemetn to stop processing
//check the request path and request QueryString
if (request.Path.Value.StartsWith("/Details", StringComparison.OrdinalIgnoreCase) && request.QueryString.Value.StartsWith("?content", StringComparison.OrdinalIgnoreCase))
{
if (request.QueryString.Value.Split('&')[1].StartsWith("id", StringComparison.OrdinalIgnoreCase))
{
var response = context.HttpContext.Response;
response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Result = RuleResult.EndResponse;
//change it with needed path, it is combined with request path and request QueryString
response.Headers[HeaderNames.Location] = "/Home/Privacy";
}
}
}

Related

ASP.NET Core OpenIdConnect and admin consent on the same callback path

I have implemented an OpenIdConnect with Azure. The code is approximately like this:
var options = new OpenIdConnectOptions
{
SignInScheme = PersistentSchemeName,
CallbackPath = "/oauth2office2",
ClientId = pubConf.ApplicationId,
Authority = $"https://login.microsoftonline.com/{configuration.TenantId}"
};
It works perfectly.
But I also need admin consent and I don't want my users to add two CallbackPaths into my app.
So I crafted admin consent url manually.
And added a redirect so it won't conflict with a OpenId middleware:
app.UseRewriter(new RewriteOptions().Add(context =>
{
var request = context.HttpContext.Request;
if (request.Path.StartsWithSegments("/oauth2office2") && request.Method == HttpMethods.Get)
{
request.Path = "/oauth2office";
}
}));
Now i have a controller at /oauth2office that does some extra stuff for me (actually gets tenant id).
Question - is there a way I can achieve it with OpenIdConnect middleware? While still being on the same callback path.
Because adding two paths is an extra i want to avoid.
I'm not even sure I can make OpenIdConnect work with admin consent actually.
One option is to add two AddOpenIDConnect(...) instances with different schema names and different callback endpoints?
You can only have one endpoint per authentication handler.
Also, do be aware that the callback request to the openidconnect handler is done using HTTP POST, like
POST /signin-oidc HTTP/1.1
In your code you are looking for a GET
if (request.Path.StartsWithSegments("/oauth2office2") && request.Method == HttpMethods.Get)
This can be done with a single OpenIdConnect handler by overriding the events RedirectToIdentityProvider and MessageReceived.
public override async Task RedirectToIdentityProvider(RedirectContext context)
{
if (!context.Properties.Items.TryGetValue("AzureTenantId", out var azureTenantId))
azureTenantId = "organizations";
if (context.Properties.Items.TryGetValue("AdminConsent", out var adminConsent) && adminConsent == "true")
{
if (context.Properties.Items.TryGetValue("AdminConsentScope", out var scope))
context.ProtocolMessage.Scope = scope;
context.ProtocolMessage.IssuerAddress =
$"https://login.microsoftonline.com/{azureTenantId}/v2.0/adminconsent";
}
await base.RedirectToIdentityProvider(context);
}
public override async Task MessageReceived(MessageReceivedContext context)
{
// Handle admin consent endpoint response.
if (context.Properties.Items.TryGetValue("AdminConsent", out var adminConsent) && adminConsent == "true")
{
if (!context.ProtocolMessage.Parameters.ContainsKey("admin_consent"))
throw new InvalidOperationException("Expected admin_consent parameter");
var redirectUri = context.Properties.RedirectUri;
var parameters = context.ProtocolMessage.Parameters.ToQueryString();
redirectUri += redirectUri.IndexOf('?') == -1
? "?" + parameters
: "&" + parameters;
context.Response.Redirect(redirectUri);
context.HandleResponse();
return;
}
await base.MessageReceived(context);
}
Then when you need to do admin consent, craft a challenge with the correct properties:
public IActionResult Register()
{
var redirectUrl = Url.Action("RegisterResponse");
var properties = new OpenIdConnectChallengeProperties
{
RedirectUri = redirectUrl,
Items =
{
{ "AdminConsent", "true" },
{ "AdminConsentScope", "https://graph.microsoft.com/.default" }
}
};
return Challenge(properties, "AzureAd");
}
public IActionResult RegisterResponse(
bool admin_consent,
string tenant,
string scope)
{
_logger.LogInformation("Admin Consent for tenant {tenant}: {admin_consent} {scope}", tenant, admin_consent,
scope);
return Ok();
}

Add trailing slash at the end of URL

I'm using .NET Core 2.1. I'm trying to permanent redirect any non-file URLs (does not contain a dot) without an ending slash to a corresponding URL with the ending slash. Is there a way to accomplish this using rewriteOptions?
var rewriteOptions = new RewriteOptions()
.AddRedirect("^[^.]*$", "$1", 301)
.AddRedirectToHttpsPermanent();
The pattern should match :
/js # has no preceding segements
/www/js # has a segment
/www/test.d/js # contains a dot within preceding segments
and should not match :
/www/js/jquery.js # contains a dot at last segment
/www/js/ # has a trailing slash
/www/js/hello.d/jquery.js/ # has a trailing slash
So create such a pattern as below :
var pattern = #"^(((.*/)|(/?))[^/.]+(?!/$))$";
var options = new RewriteOptions()
.AddRedirect(pattern, "$1/",301);
and it will work .
Test cases :
should redirect :
GET https://localhost:5001/js HTTP/1.1
GET https://localhost:5001/xx/js HTTP/1.1
GET https://localhost:5001/xx/test.d/js HTTP/1.1
should not redirect :
GET https://localhost:5001/xx/js/ HTTP/1.1
GET https://localhost:5001/xx/jquery.js HTTP/1.1
GET https://localhost:5001/xx/hello.d/jquery.js HTTP/1.1
According to this documentation to add a trailing slash it's:
var options = new RewriteOptions()
.AddRedirect("(.*[^/])$", "$1/")
.AddRedirectToHttpsPermanent();
To prevent it happening to static files, make sure app.UseStaticFiles(); is called before app.UseRewriter(options);
so:
// Return static files and end the pipeline.
app.UseStaticFiles(); // <--- **before** the redirect
var options = new RewriteOptions()
.AddRedirect("(.*[^/])$", "$1/")
.AddRedirectToHttpsPermanent();
app.UseRewriter(options);
Calling UseStaticFiles() first will short-cut the pipeline for static files. So no redirects are done on the static files.
More info on Startup.Configure order here:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-2.1#order
Way for adding trailing slash for non file URL's is by configuring the middleware in startup.cs after app.UseStaticFiles();
public class TrailingSlashUrlMiddleware
{
private readonly RequestDelegate _next;
public TrailingSlashUrlMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
string currentPath = httpContext.Request.Path;
if (currentPath != "/")
{
string ext = System.IO.Path.GetExtension(currentPath);
bool NeedRedirection = false;
var currentQueryString = httpContext.Request.QueryString;
if (string.IsNullOrEmpty(ext))
{
if (!currentPath.EndsWith("/"))
{
currentPath = currentPath + "/";
NeedRedirection = true;
}
//if (currentQueryString.HasValue)
//{
// string queryString = currentQueryString.Value;
// if (!queryString.EndsWith("/"))
// {
// queryString = queryString + "/";
// NeedRedirection = true;
// }
// currentPath += queryString;
//}
}
if (NeedRedirection)
{
httpContext.Response.Redirect(currentPath, true);
return;
}
}
await _next(httpContext);
}
}
if you need to apply this to query string uncomment the commented code.

How do I force https redirect on static files in ASP.NET Core 2.1?

I have a ASP.NET Core app that has HTTPS redirection enabled. The problem is that HTTPS redirection doesn´t work on static files, so the front-end is in a different port than the back-end when I enter the site with http and this causes CORS to block all requests made to the back-end. I tried allowing CORS to all requests (for testing, of course) but no matter what I did, the requests failed. Only visiting the front end with https worked.How do I force https redirect on static files with ASP.NET Core?
Based on my expeirence, you must add this code to the startup.cs file
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpsRedirection(options =>
{
options.HttpsPort = 443;
});
You have to love Microsoft documentation. 6 paragraphs below the incomplete example they tell you about this requirement:
If no port is set:
Requests aren't redirected.
You can set port using the following techniques:
The port can be configured by setting the:
ASPNETCORE_HTTPS_PORT environment variable.
http_port host configuration key (for example, via hostsettings.json or a command
line argument).
HttpsRedirectionOptions.HttpsPort. See the preceding
This worked for me.
app.UseStaticFiles(new StaticFileOptions()
{
OnPrepareResponse = (context) =>
{
var request = context.Context.Request;
var response = context.Context.Response;
UrlRewriteUtils.RedirectIfHttp(request, response);
}
});
And here is the Utility method.
public class UrlRewriteUtils
{
public static void RedirectIfHttp(HttpRequest request, HttpResponse response)
{
string reqProtocol;
if (request.Headers.ContainsKey("X-Forwarded-Proto"))
reqProtocol = request.Headers["X-Forwarded-Proto"][0];
else if (request.IsLocal())
reqProtocol = "https";
else
reqProtocol = request.IsHttps ? "https" : "http";
if (reqProtocol.ToLower() != "https")
{
var newUrl = new StringBuilder()
.Append("https://").Append(request.Host)
.Append(request.PathBase).Append(request.Path)
.Append(request.QueryString);
response.Redirect(newUrl.ToString(), true);
}
}
}

dotnet core Http Response Header Content-Type lost

I have created a WebApi server solution that includes .Net Core 2.0.
First, I was define special case of error use HttpStatusCode 550.
In case of nomally that is HttpStatusCode 200 that is included with "Content-Type:application/json" in response header.
By the way, In case of HttpStatusCode 550 that is not included with "Content-Type:application/json" in response header.
This is true even if I create the middleware in the solution and do the following:
Invoking in PostMan that will result in a Json format case of HttpStatusCode 200.
But, it is not json format on HttpStatusCode 550.
So, that is broken Korean language in this case.
Append, that is no problem in local windows 10 machine & ubuntu 16.0.4 machine,
It just raise in server machine in ubuntu 16.0.4.
Is there a way to solve it?
[Startup.cs]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseStaticFiles();
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API");
});
// CORS Middleware
app.UseMiddleware<HttpMiddleware>();
app.UseMvc();
}
[HttpMiddleware.cs]
public async Task Invoke(HttpContext context)
{
context.Response.ContentType = "application/json";
context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
context.Response.Headers.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
context.Response.Headers.Add("Access-Control-Allow-Headers", "X-Requested-With,content-type");
context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
await _next(context);
}
In addition, I set the result value as below.
[myControll.cs]
errorresult _errorresult = new errorresult();
JsonResult _ObjectResult;
try
{
:
[ source code ]
:
// success case
// _product is result object
_product.resultcode = "200";
_product.resultmessage = _CommonLogic.GetResponseMessage(_PurchaseMessage);
_product.resultoptional = "";
_product.processtime = Convert.ToString(_stopwatch.ElapsedMilliseconds);
_stopwatch.Stop();
_ObjectResult = _CommonLogic.GetObjectResult(_product, 200);
}
catch(Exception ex)
{
_errorresult = new errorresult();
_errorresult.resultcode = "550";
_errorresult.resultmessage = ex.Message.ToString();
_errorresult.resultoptional = "";
_errorresult.processtime = Convert.ToString(_stopwatch.ElapsedMilliseconds);
_stopwatch.Stop();
_ObjectResult = _CommonLogic.GetObjectResult(_errorresult, 550);
}
[CommonLogic.cs]
public JsonResult GetObjectResult(object obj,int iStatusCode)
{
JsonResult _ObjectResult = new JsonResult(obj);
_ObjectResult.StatusCode = iStatusCode;
// _ObjectResult.ContentTypes.Add(new MediaTypeHeaderValue("application/json; charset=utf-8"));
// _ObjectResult.ContentType = "application/json; charset=utf-8";
return _ObjectResult;
}
Thanks for reading of my problem.

Redirect Non WWW to WWW [duplicate]

This question already has answers here:
Redirect Non WWW to WWW using Asp.Net Core Middleware
(10 answers)
Closed 4 years ago.
On an ASP.Net Core application startup I have:
RewriteOptions rewriteOptions = new RewriteOptions();
rewriteOptions.AddRedirectToHttps();
applicationBuilder.UseRewriter(rewriteOptions);
When in Production I need to redirect all Non WWW to WWW.
For example:
domain.com/contact > www.domain.com/contact
How can I do this using Rewrite Middleware?
You could create a custom rule by creating a class and implementing the IRule interface (see below). I think there must be a better way to construct the redirectUrl though.
public class CanonicalDomainRewriteRule : IRule
{
public void ApplyRule(RewriteContext context)
{
HttpRequest request = context.HttpContext.Request;
if (request.Host.Value.StartsWith("www.", StringComparison.OrdinalIgnoreCase))
{
return;
}
else
{
HttpResponse response = context.HttpContext.Response;
string redirectUrl = $"{request.Scheme}://www.{request.Host}{request.Path}{request.QueryString}";
response.Headers[HeaderNames.Location] = redirectUrl;
response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Result = RuleResult.EndResponse;
}
}
}
You can add this rule as follows in your ConfigureServices method:
RewriteOptions rewriteOptions = new RewriteOptions()
.Add(new CanonicalDomainRewriteRule());
app.UseRewriter(rewriteOptions);
See Extensions and Options: IRule-based rule for further examples.