I have created a web api that uses the JWT system using this article here. When calling the API from a REST client it works just fine. However when trying to access it from a browser it gives a CORS error since it doesn't send out the correct response headers.
Startup.cs
app.UseCors(CorsOptions.AllowAll);
Note that on my controllers CORS works just fine, it just breaks for the OAuthAuthorizationServer.
CustomOAuthProvider.cs
public override Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
var user = Database.Users.FirstOrDefault(u => u.Email == context.UserName);
if (user == null || !BCrypt.Net.BCrypt.Verify(context.Password, user.Password))
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return Task.FromResult<object>(null);
}
var companyId = int.Parse(context.OwinContext.Get<string>("company_id"));
var company = user.Companies.FirstOrDefault(c => c.Id == companyId);
if (company == null)
{
context.SetError("invalid_grant", "You don't belong to that company!");
return Task.FromResult<object>(null);
}
var identity = new ClaimsIdentity("JWT");
identity.AddClaim(new Claim("uue", user.Email));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{ "audience", company.ServerUrl }
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
return Task.FromResult<object>(null);
}
However after making the call to obtain the token, I only get back these response headers.
Content-Length:1245
Content-Type:text/html
Date:Wed, 20 Apr 2016 20:34:40 GMT
Server:Microsoft-IIS/8.5
X-Powered-By:ASP.NET
Is there something I'm doing wrong?
Note: I'm assuming you are using the same Startup.cs code defined in the liked tutorial.
Try to move the call to app.UseCors(CorsOptions.AllowAll); at the top of your Configuration method in Startup.cs:
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
HttpConfiguration config = new HttpConfiguration();
// Web API routes
config.MapHttpAttributeRoutes();
ConfigureOAuth(app);
app.UseWebApi(config);
}
In Owin every middleware in the pipeline is executed only if the preceding passes through the invocation. For this reason app.UseCors is executed only after the AuthenticationMiddleware (in your case OAuthAuthorizationServer) and only if it does not stop the flow in the pipeline (e.g. OAuth returns a response).
Moving the Cors middleware declaration before other middlewares ensures you that it is executed for each request.
Make sure you allow CORS in web config
<httpProtocol>
<customHeaders>
<clear />
<add name="Access-Control-Allow-Methods" value="GET,PUT,POST,OPTIONS,DEBUG" />
<add name="Access-Control-Allow-Origin" value="*" />
<add name="Access-Control-Allow-Headers" value="authorization,content-type" />
</customHeaders>
</httpProtocol>
Related
I need to add single-sign-on using Windows Authentication to my intranet Angular web application (hosted on IIS) which uses a JWT Bearer token for authentication. The controllers are secured using the [Authorize] attribute and JWT Bearer token authentication is working. All of the controllers are exposed under the api/ route.
The idea is to publish a new SsoController under the sso/ route, which should be secured with Windows Authentication and that exposes a WindowsLogin action that returns a valid bearer token for the application.
Back when I was using ASP.net Web Forms it was quite easy, you only had to enable Windows Authentication in the web.config/system.webServer section, disable it application-wide in the system.web section and then enable it again under a <location path="sso"> tag. This way ASP.net generated the NTLM/Negotiate challenges only for requests under the sso route.
I got it almost working - the SsoController gets the Windows user name and creates the JWT token just fine, but the pipeline is still generating the WWW-Authenticate: NTLM and WWW-Authenticate: Negotiate headers for all HTTP 401 responses, not just for the ones under the sso route.
How can I tell the pipeline that I want only Anonymous or Bearer auth for all of the api/ requests?
Thanks in advance for your help.
Program.cs
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseIISIntegration();
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Set up data directory
services.AddDbContext<AuthContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("AuthContext")));
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "AngularWebApp.Web",
ValidAudience = "AngularWebApp.Web.Client",
IssuerSigningKey = _signingKey,
ClockSkew = TimeSpan.Zero //the default for this setting is 5 minutes
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseAuthentication();
app.UseWhen(context => context.Request.Path.StartsWithSegments("/sso"),
builder => builder.UseMiddleware<WindowsAuthMiddleware>());
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
// To learn more about options for serving an Angular SPA from ASP.NET Core,
// see https://go.microsoft.com/fwlink/?linkid=864501
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
WindowsAuthMiddleware.cs
public class WindowsAuthMiddleware
{
private readonly RequestDelegate next;
public WindowsAuthMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
if (!context.User.Identity.IsAuthenticated)
{
await context.ChallengeAsync(IISDefaults.AuthenticationScheme);
return;
}
await next(context);
}
}
web.config
<system.webServer>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" forwardWindowsAuthToken="true"/>
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="true" />
</authentication>
</security>
</system.webServer>
So, I spent the last few days investigating this problem and I got a working - if a bit hacky - solution.
It turns out that the main problem is that IIS will handle the Windows Authentication negotiation for all 401 responses sent by the application. It's something that's done at a lower level as soon as you enable Windows Authentication in IIS (or in the system.webServer section), and I haven't been able to find a way to bypass this behaviour. I actually did a test with a classic Web Form app and it works the same - the reason I never noticed this is that classic Forms Authentication rarely generates 401 responses, rather it uses redirects (30x) to take the user to the login page.
This gave me an idea: I could add another middleware to the pipeline that rewrites 401 responses generated by the authorization infrastructure to another, rarely used HTTP code, and detect that in my client Angular app to make it behave as a 401 (by refreshing an access token, or denying router navigation, etc). I used HTTP error 418 "I'm a teapot" since it's an existing but unused code. Here is the code:
ReplaceHttp401StatusCodeMiddleware.cs
public class ReplaceHttp401StatusCodeMiddleware
{
private readonly RequestDelegate next;
public ReplaceHttp401StatusCodeMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext context)
{
await next(context);
if (context.Response.StatusCode == 401)
{
// Replace all 401 responses, except the ones under the /sso paths
// which will let IIS trigger the Windows Authentication mechanisms
if (!context.Request.Path.StartsWithSegments("/sso"))
{
context.Response.StatusCode = 418;
context.Response.Headers["X-Original-HTTP-Status-Code"] = "401";
}
}
}
}
Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
...
// Enable the SSO login using Windows Authentication
app.UseWhen(
context => context.Request.Path.StartsWithSegments("/sso"),
builder => builder.UseMiddleware<WindowsAuthMiddleware>());
app.UseMiddleware<ReplaceHttp401StatusCodeMiddleware>();
...
}
The middleware also injects the original status code in the response for further reference.
I also applied to my code the suggestion from Mickaƫl Derriey to use Authorization policies because it makes the controllers cleaner, but it's not necessary for the solution to work.
Welcome to StackOverflow! That's an interesting quesiton you have here.
First, let me state that I didn't test any of the content in this answer.
Using authorization policies to drive sources of authentication
I like the idea behind the WindowsAuthMiddleware you created, and how it's conditionally inserted in the pipeline if the URL starts with /sso.
MVC integrated with the authorization system and provides the same capabilities with authorization policies. The result is the same, and prevents you from having to write low-level code.
You can define authorization policies in the ConfigureServices method. In your case, if I'm not mistaken, there are two policies:
all requests to /sso should be authenticated with Windows authenticated; and
all other requests should be authenticated with JWTs
services.AddAuthorization(options =>
{
options.AddPolicy("Windows", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(IISDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build());
options.AddPolicy("JWT", new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build());
});
You can then reference those policies by name in the [Authorize] attributes used to decorate your controllers and/or actions.
[Authorize("Windows")]
public class SsoController : Controller
{
// Actions
}
[Authorize("JWT")]
public class ApiController : Controller
{
// Actions
}
Doing so means that the Windows authentication handler will not run against /api requests, hence the responses should not contain the WWW-Authenticate: NTLM and WWW-Authenticate: Negotiate headers.
Removing automatic authentication of all requests
When you pass an authentication scheme as an argument of AddAuthentication, this means the authentication middleware will try to authenticate every request against that scheme.
This is useful when you have one authentication scheme, but in this case, you could think about removing it, as even for requests to /sso, the JWT handler will analyze the request for a token.
Two calls to AddAuthentication
You should only have one call to AddAuthentication:
the first one sets the IIS authentication scheme as a default so the handler should run on every request;
the second call overwrites that setting and set the JWT scheme as the default one
Let me know how you go!
I am looking to use Auth0 as the authentication provider for ServiceStack. There is a great sample application documented at Auth0 which applies & works well when working with ServiceStack and using ServiceStack.Host.MVC: https://auth0.com/docs/quickstart/webapp/servicestack/01-login.
However, I am at a loss how to construct the authorization URL and redirect the user to that URL in a scenario where I am NOT using MVC & the AccountController to redirect the user. How can I construct the redirect URLs using ServiceStack Auth Plugin, if I want to replicate the logic as per MVC sample code below:
public class AccountController : Controller
{
public ActionResult Login()
{
string clientId = WebConfigurationManager.AppSettings["oauth.auth0.AppId"];
string domain = WebConfigurationManager.AppSettings["oauth.auth0.OAuthServerUrl"].Substring(8);
var redirectUri = new UriBuilder(this.Request.Url.Scheme, this.Request.Url.Host, this.Request.Url.IsDefaultPort ? -1 : this.Request.Url.Port, "api/auth/auth0");
var client = new AuthenticationApiClient(new Uri($"https://{domain}"));
var authorizeUrlBuilder = client.BuildAuthorizationUrl()
.WithClient(clientId)
.WithRedirectUrl(redirectUri.ToString())
.WithResponseType(AuthorizationResponseType.Code)
.WithScope("openid profile")
.WithAudience($"https://{domain}/userinfo");
return Redirect(authorizeUrlBuilder.Build().ToString());
}
}
For all who are interested,here is the solution I ended up adopting.
Steps:
1) Create an Auth0 plugin (see gist here)
2) Register the Plugin in your AppHost.
Plugins.Add(new AuthFeature(() => new Auth0UserSession(), new IAuthProvider[] {
new Auth0Provider(appSettings,appSettings.GetString("oauth.auth0.OAuthServerUrl"))
}));
3) Add the relevant keys in your Web.Config.
<appSettings>
<add key="oauth.auth0.OAuthServerUrl" value="https://xxxxxxx.auth0.com" />
<add key="oauth.auth0.AppId" value="xxxxxx" />
<add key="oauth.auth0.AppSecret" value="xxxxxxx" />
</appSettings>
I have a Service Stack service hosted within a SharePoint 2013 site. When attempting to make a cross domain request to one of the services, a preflight OPTIONS request is made, as expected.
The problem is that the response always comes back as 401 Unauthorized, due to the fact that authentication info is not sent across with the request. I have tried putting some request filters via servicestack to try and bypass the authentication, but these filters are not firing - it seems like something prior to service stack is sending the response.
Is there any way of specifying that OPTIONS requests to the sharepoint site do not need to be authenticated? If not, does anyone have a workaround for this scenario?
I tried 'fooling' the browser in to not sending a preflight request by changing the data type from application/json to text/plain in my ajax request, but then the data I send is not being deserialised in to the correct RequestDTO for the service calls on the server side.
Any help would be appreciated.
We ended up having to write our own HTTP module in order to support the options request. We basically add a key specifying which domains to allow the CORS requests from (can support more than one) and then have this HTTP module registered:
public class ECSPreFlightModule : IHttpModule
{
/// <summary>
/// You will need to configure this module in the Web.config file of your
/// web and register it with IIS before being able to use it. For more information
/// see the following link: http://go.microsoft.com/?linkid=8101007
/// </summary>
public void Dispose()
{
//clean-up code here.
}
private const string OptionsHeader = "OPTIONS";
private const string OriginHeader = "ORIGIN";
private const string AccessAllowOrigin = "Access-Control-Allow-Origin";
private string AllowedOriginUrlsArray
{
get
{
return GetWebConfigValue("CORSAllowedOriginUrls");
}
}
private string GetWebConfigValue(string key)
{
var configuration = WebConfigurationManager.OpenWebConfiguration("~");
object o = configuration.GetSection("system.web/httpModules");
var section = o as HttpModulesSection;
return section.CurrentConfiguration.AppSettings.Settings[key].Value;
}
public void Init(HttpApplication context)
{
context.PreSendRequestHeaders += (sender, e) =>
{
var splitUrls = AllowedOriginUrlsArray.Split('|');
var response = context.Response;
var originHeader = context.Request.Headers.Get(OriginHeader);
if (!String.IsNullOrEmpty(originHeader) && splitUrls.Length > 0)
{
foreach (var url in splitUrls)
{
var urlLower = url.ToLower();
var originHeaderLower = originHeader.ToLower();
// if the method being requested is an OPTIONS request and the url is the url specified in the web.config then return an OK response.
if (context.Request.HttpMethod.ToLowerInvariant() == OptionsHeader.ToLowerInvariant() &&
(urlLower == originHeaderLower))
{
response.StatusCode = (int)HttpStatusCode.OK;
}
// If the originating header url is equal to the url specified in the web.config then grant the access control
if (originHeaderLower == urlLower)
{
response.AddHeader(AccessAllowOrigin, originHeader);
break;
}
}
}
};
}
}
}
The above module was wrapped in a sharepoint feature that, when activated, made the appropriate changes to the web.config, namely registering the module and adding the following keys:
<add name='Access-Control-Allow-Credentials' value='true' />
<add name='Access-Control-Allow-Headers' value='Authorization, X-Requested-With, Content-Type, Origin, Accept, X-RequestDigest' />
<add name='Access-Control-Allow-Methods' value='GET,POST,OPTIONS,PUT, DELETE' />
I have an API service made with NancyFX, and a couple of front-end developers creating an SPA JS client against this API.
We would like to test the client side code against the published server without having to publish the client code with too much frequency.
But, the client runs at localhost, and the server is at Windows Azure.
Is it possible and easy to enable CORS on the NancyFX server? How can I do that?
Thanks.
Its possible to do this in the bootstraper of Nancy
protected override void RequestStartup(TinyIoCContainer container, IPipelines pipelines, NancyContext context)
{
//CORS Enable
pipelines.AfterRequest.AddItemToEndOfPipeline((ctx) =>
{
ctx.Response.WithHeader("Access-Control-Allow-Origin", "*")
.WithHeader("Access-Control-Allow-Methods", "POST,GET")
.WithHeader("Access-Control-Allow-Headers", "Accept, Origin, Content-type");
});
If you're using IIS to host Nancy, in this case on Windows Azure then you can just update the web.config to add the header to every request.
This can be done by adding the following:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
</system.webServer>
Alternatively you can do what Sunny suggested, and if you don't like writing that every time you can add your own extension method:
public static class NancyExtensions
{
public static void EnableCors(this NancyModule module)
{
module.After.AddItemToEndOfPipeline(x =>
{
x.Response.WithHeader("Access-Control-Allow-Origin", "*");
});
}
}
Then you can just call this.EnableCors() in your route.
If your HTTP request is simple then Phill's answer will suffice, but if the request is not so simple, the browser will send a preflight check. The preflight check is an OPTIONS HTTP request and this has to be handled too.
Here is an extension method to configure CORS:
public static class MyNancyExtension
{
public static void EnableCORS(this Nancy.Bootstrapper.IPipelines pipelines)
{
pipelines.AfterRequest.AddItemToEndOfPipeline(ctx =>
{
if (ctx.Request.Headers.Keys.Contains("Origin"))
{
var origins = "" + string.Join(" ", ctx.Request.Headers["Origin"]);
ctx.Response.Headers["Access-Control-Allow-Origin"] = origins;
if (ctx.Request.Method == "OPTIONS")
{
// handle CORS preflight request
ctx.Response.Headers["Access-Control-Allow-Methods"] =
"GET, POST, PUT, DELETE, OPTIONS";
if (ctx.Request.Headers.Keys.Contains("Access-Control-Request-Headers"))
{
var allowedHeaders = "" + string.Join(
", ", ctx.Request.Headers["Access-Control-Request-Headers"]);
ctx.Response.Headers["Access-Control-Allow-Headers"] = allowedHeaders;
}
}
}
});
}
}
To enable CORS call this extension method in the bootstrapper:
protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
{
base.ApplicationStartup(container, pipelines);
pipelines.EnableCORS();
}
Please note it is not extending NancyModule because OPTIONS is handled outside of module (also here).
I am setting up facebook authentication with servicestack and have been getting the return type #f=Unknown, I've tracked it down to coming from the authentication block:
try
{
var contents = accessTokenUrl.DownloadUrl();
var authInfo = HttpUtility.ParseQueryString(contents);
tokens.AccessTokenSecret = authInfo["access_token"];
session.IsAuthenticated = true;
authService.SaveSession(session, SessionExpiry);
OnAuthenticated(authService, session, tokens, authInfo.ToDictionary());
//Haz access!
return authService.Redirect(session.ReferrerUrl.AddHashParam("s", "1"));
}
catch (WebException we)
{
var statusCode = ((HttpWebResponse)we.Response).StatusCode;
if (statusCode == HttpStatusCode.BadRequest)
{
return authService.Redirect(session.ReferrerUrl.AddHashParam("f", "AccessTokenFailed"));
}
}
//Shouldn't get here
return authService.Redirect(session.ReferrerUrl.AddHashParam("f", "Unknown"));
The reason for it dropping through is the catch checks the response status code. In my scenario I am receiving 407 Proxy Authentication Required.
I've tracked it down further to the line:
var contents = accessTokenUrl.DownloadUrl();
Can anybody help with how I put in place the required proxy authentication?
For info, my app is running in a windows environment, it is run as an windows authenticated user so has permission access the proxy server, I just need to tell the code to use this - or any - credentials.
Thanks in anticipation
You can try setting the default proxy for .NET applications by setting the <defaultProxy/> in your Web.Config:
<configuration>
<system.net>
<defaultProxy>
<proxy
usesystemdefaults="true"
proxyaddress="http://192.168.1.10:3128"
bypassonlocal="true"
/>
<bypasslist
<add address="[a-z]+\.contoso\.com" />
</bypasslist>
</defaultProxy>
</system.net>
</configuration>