In my Web API 2 project I have JWT based authentication. The configuration is
var config = new HttpConfiguration();
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions {/**/});
config.Filters.Add(new MyAuthenticationFilter());
app.UseWebApi(config);
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
MyAuthenticationFilter builds a custom principal based on an incoming JWT and attaches it to a request so that in my controller actions I can access it as this.User. Everything is working fine except the filter is ignored for SignalR requests:
public class MyHub : Hub {
public override Task OnConnected() {
// here Context.User is ClaimsPrincipal which contain a JWT from Authorization header
// I expect it to be MyCustomPrincipal
return base.OnConnected();
}
}
Why IAuthenticationFiltter is ignored? How do I fix that?
You cant use jwt on header with signalR. Send jwt in qs (query string) and add some customization on api that code below.
App_Start/Startup.Auth.cs
public void ConfigureAuth(IAppBuilder app)
{
// SignalR Auth0 custom configuration.
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions()
{
Provider = new OAuthBearerAuthenticationProvider()
{
OnRequestToken = context =>
{
if (context.Request.Path.Value.StartsWith("/signalr"))
{
string bearerToken = context.Request.Query.Get("token");
if (bearerToken != null)
{
string[] authorization = new string[] { "bearer " + bearerToken };
context.Request.Headers.Add("Authorization", authorization);
}
}
return null;
}
}
});
}
and you use Authorize attribute on hub
[Authorize]
public class MyHub:Hub
Related
I have an ASP.NET Web API project in (running on .NET 4.8 - not .NET Core). I have setup 'Bearer token' authentication by adding:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType))
}
}
And in Startup.cs:
public class Startup1
{
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = "https://www.example.com", //some string, normally web url,
}
});
}
}
And in my controller method, I add [Authorize] in my controller API.
But when I call the endpoint in my browser, I checked that it has a bearer token in the http header. The body of the http response is
"Message":"Authorization has been denied for this request."
How can I debug my issue? as I don't see any exception or any message in log.
Could you add these parameters in your code and try:
app.UseJwtBearerAuthentication(new JwtBearerAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
AllowedAudiences = new[] { "Any" },
IssuerSecurityKeyProviders = new IIssuerSecurityKeyProvider[] {
new SymmetricKeyIssuerSecurityKeyProvider(issuer, secret)
}
});
var issuer = ConfigurationManager.AppSettings["issuer"];
var secret = TextEncodings.Base64Url.Decode(ConfigurationManager.AppSettings["secret"]);
Add these parameters in App config file:
<appSettings>
<add key="issuer" value="http://localhost:xxx/"/>
<add key="secret" value="YourToken"/>
</appSettings>
Here is Startup.cs from SignInWithAppleSample:
public void ConfigureServices(IServiceCollection services)
{
// ...
services
.AddAuthentication(options => options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/signin";
options.LogoutPath = "/signout";
})
.AddApple(options =>
{
options.ClientId = Configuration["AppleClientId"];
options.KeyId = Configuration["AppleKeyId"];
options.TeamId = Configuration["AppleTeamId"];
options.UsePrivateKey(
(keyId) =>
Environment.ContentRootFileProvider.GetFileInfo($"AuthKey_{keyId}.p8"));
});
// ...
}
public void Configure(IApplicationBuilder app)
{
// ...
app.UseAuthentication();
// ...
}
Here's controller's method, which uses for authentication:
public class AuthenticationController : Controller
{
[HttpPost("~/signin")]
public IActionResult SignIn()
=> Challenge(new AuthenticationProperties { RedirectUri = "/redirect/signin/apple" }, AppleAuthenticationDefaults.AuthenticationScheme);
[HttpGet("~/signout")]
[HttpPost("~/signout")]
public IActionResult SignOut()
=> SignOut(new AuthenticationProperties { RedirectUri = "/redirect/signout/apple" }, CookieAuthenticationDefaults.AuthenticationScheme);
}
It works, cookies set, I can see my claims.
In my project, I don't use built-in ASP.NET Core, I simply need to handle request on /redirect/signin/apple and return access token, identity token, etc. to my clients, and maybe check in DB that this user is already registered. Client's don't want cookies, they accept only Bearer tokens.
So, the question is, how can I get Bearer token instead of cookies?
I have an api build in .net core 2.1. To restrict access to various endpoints, I use IdentityServer4 and [Authorize] attribute. However, my goal during development is to expose the api swagger documentation to our developers so that they may use it no matter where they work from. The challenge that I face is how do I protect the swagger index.html file so that only they can see the details of the api.
I have created a custom index.html file in the wwwroot/swagger/ui folder and that all works, however, that file uses data from /swagger/v1/swagger.json endpoint which is not protected. I would like to know how can I override the return value for that specific endpoint so that I may add my own authentication to it?
EDIT:
Currently, I have achieved the above with the following middleware:
public class SwaggerInterceptor
{
private readonly RequestDelegate _next;
public SwaggerInterceptor(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var uri = context.Request.Path.ToString();
if (uri.StartsWith("/swagger/ui/index.html"))
{
var param = context.Request.QueryString.Value;
if (!param.Equals("?key=123"))
{
context.Response.StatusCode = 404;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("{\"result:\" \"Not Found\"}", Encoding.UTF8);
return;
}
}
await _next.Invoke(context);
}
}
public class Startup
{
//omitted code
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<SwaggerInterceptor>();
//omitted code
}
}
What I don't like about this approach as it will inspect every single request. Is there a better way to achieve this? The above only protects the index.html file, but I can adjust it to protect the json endpoint in the similar fashion.
You can choose some options:
basic authorization
OpenId Connect authorization using identity server
Basic Authorization
In this case you just close your swagger endpoints.
// Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication()
.AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>("Basic", _ => {});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
...
var pipeline = endpoints.CreateApplicationBuilder().Build();
var basicAuthAttr = new AuthorizeAttribute { AuthenticationSchemes = "Basic" };
endpoints
.Map("/swagger/{documentName}/swagger.json", pipeline)
.RequireAuthorization(basicAuthAttr);
endpoints
.Map("/swagger/index.html", pipeline)
.RequireAuthorization(basicAuthAttr);
});
}
}
// BasicAuthenticationHandler.cs
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
...
}
OIDC Authorization Using IdentityServer4
I have written the article for this case: https://medium.com/dev-genius/csharp-protecting-swagger-endpoints-82ae5cfc7eb1
Here it is using OpenIdConnect and Swashbuckle in Asp.Net Core 3.1. Now, if I type https://myurl.com/swagger I get routed to my normal login page and after logging in successfully, I can see the swagger.
public class Startup
{
//<snip/>
public void Configure(IApplicationBuilder app)
{
//<snip/>
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("v1/swagger.json", "Some name"); });
app.UseEndpoints(routes =>
{
var pipeline = routes.CreateApplicationBuilder().Build();
routes.Map("/swagger", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
routes.Map("/swagger/index.html", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
routes.Map("/swagger/v1/swagger.json", pipeline).RequireAuthorization(new AuthorizeAttribute { AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme });
routes.Map("/swagger/{documentName}/swagger.json", pipeline).RequireAuthorization(new AuthorizeAttribute { AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme });
routes.MapDefaultControllerRoute();
});
}
}
EDIT:
Somehow I thought the below was working, but when I retested it later it turned out that actually it was giving error: The request reached the end of the pipeline without executing the endpoint. So, I changed to include a fixed set of endpoints under /swagger that contain afaik the key data.
routes.Map("/swagger/{**any}", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
Note: this {**any} part of the route template protects all files under /swagger as well, so for example /swagger/index.html, /swagger/v1/swagger.json etc etc.
I believe your best option is what you already did. Build your own middleware, as I don't know any middleware for validate authentication on static files. You could add the basePath to avoid enter at this specific middleware when its not necessary. Like the code below
app.Map("/swagger", (appBuilder) =>
{
appBuilder.UseMiddleware<SwaggerInterceptor>();
});
Also this article could help you to build a more generic middleware for validate authentication on static files.
https://odetocode.com/blogs/scott/archive/2015/10/06/authorization-policies-and-middleware-in-asp-net-5.aspx
After some search I solved and it works for me.
you must define a middle-ware class to authenticate the user whom wants to visit your swagger-ui view page like this.
public class SwaggerBasicAuthMiddleware
{
private readonly RequestDelegate next;
public SwaggerBasicAuthMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/swagger"))
{
string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic "))
{
// Get the credentials from request header
var header = AuthenticationHeaderValue.Parse(authHeader);
var inBytes = Convert.FromBase64String(header.Parameter);
var credentials = Encoding.UTF8.GetString(inBytes).Split(':');
var username = credentials[0];
var password = credentials[1];
// validate credentials
if (username.Equals("Test") && password.Equals("Test"))
{
await next.Invoke(context).ConfigureAwait(false);
return;
}
}
context.Response.Headers["WWW-Authenticate"] = "Basic";
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
}
else
{
await next.Invoke(context).ConfigureAwait(false);
}
}
}
For simplicity I am using hardcoded credentials but same can enhanced to use it from database also.
Also I have to create a extension method in a static class like this
public static class AuthorizedSampleClass
{
public static IApplicationBuilder UseSwaggerAuthorized( this IApplicationBuilder builder )
{
return builder.UseMiddleware<SwaggerBasicAuthMiddleware>( );
}
}
in startup.cs remove the env.IsDevelopment() part and add app.UseSwaggerAuthorized( ); right before app.UseSwagger( ); because authentication middleware will be called before accessing swagger ui.
app.UseSwaggerAuthorized( );
app.UseSwagger( );
app.UseSwaggerUI( c => c.SwaggerEndpoint( "/swagger/v1/swagger.json", "Broker.Rest v1" ) );
now press F5 and rout to swagger view, it has done.
press "Test" and "Test" as username and password to enter.
I have an Angular4 client which handles authentication with AAD using ADAL.js.
After signing in I can successfully see my username as well as my JWT token.
Now I want to connect to a WebApi2 service. I am passing the Token as bearer token in the headers, but for some reason I keep getting a 401 "Authorization has been denied" from my API.
Angular4 client
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + this.adalService.accessToken);
Request header
Response header
Startup.cs
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
ConfigureNinject(app);
}
}
Startup.Auth.cs
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"],
}
}
}
}
Startup.Ninject.cs
public partial class Startup
{
public void ConfigureNinject(IAppBuilder app)
{
var kernel = CreateKernel();
app.UseNinjectMiddleware(() => kernel).UseNinjectWebApi(WebApiConfig.GetConfiguration());
return kernel;
}
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
RegisterServices(kernel);
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
return kernel;
}
private static void RegisterServices(IKernel kernel)
{
// some bindings here
}
}
UsersController.cs
public class UsersController : ApiController
{
public async Task<IHttpActionResult> Get
{
return Ok("test");
}
}
UPDATE: RESOLUTION
As pointed out by Navya Canumalla in the comments, the clientId value used in adal.js and ida:Audience in Web.config need to be the same value. This was not the case here. I have corrected that and it works as expected now.
I've followed a tutorial to protect a Web API with OAuth in C#.
I'm doing some tests and so far I've been able to get the access token successfully from /token. I'm using a Chrome extension called "Advanced REST Client" to test it.
{"access_token":"...","token_type":"bearer","expires_in":86399}
This is what I get back from /token. Everything looks good.
My next request is to my test API Controller:
namespace API.Controllers
{
[Authorize]
[RoutePrefix("api/Social")]
public class SocialController : ApiController
{
....
[HttpPost]
public IHttpActionResult Schedule(SocialPost post)
{
var test = HttpContext.Current.GetOwinContext().Authentication.User;
....
return Ok();
}
}
}
The request is a POST and has the header:
Authorization: Bearer XXXXXXXTOKEHEREXXXXXXX
I get: Authorization has been denied for this request. returned in JSON.
I tried doing a GET as well and I get what I would expect, that the method isn't supported since I didn't implement it.
Here is my Authorization Provider:
public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { "*" });
using (var repo = new AuthRepository())
{
IdentityUser user = await repo.FindUser(context.UserName, context.Password);
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
}
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
context.Validated(identity);
}
}
Any help would be great. I'm not sure if it is the request or the code that is wrong.
edit:
Here is my Startup.cs
public class Startup
{
public void Configuration(IAppBuilder app)
{
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
ConfigureOAuth(app);
}
public void ConfigureOAuth(IAppBuilder app)
{
var oAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(oAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
Issue is pretty simple:
Change order of your OWIN pipeline.
public void Configuration(IAppBuilder app)
{
ConfigureOAuth(app);
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
}
For OWIN pipeline order of your configuration quite important. In your case, you try to use your Web API handler before the OAuth handler. Inside of it, you validate your request, found you secure action and try to validate it against current Owin.Context.User. At this point this user not exist because its set from the token with OAuth Handler which called later.
You have to add a claim with this schema:
http://schemas.microsoft.com/ws/2008/06/identity/claims/role
best thing to do is to use the pre-defined set of claims:
identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
You can find ClaimTypes in System.Security.Claims.
Another thing you have to consider is filter roles in your Controller/Action:
[Authorize(Roles="User")]
You can find a simple sample app, self-hosted owin with a jquery client here.
Looks like the version of "System.IdentityModel.Tokens.Jwt" that co-exists with the other Owin assemblies is not proper.
In case you're using "Microsoft.Owin.Security.Jwt" version 2.1.0, you ought to be using the "System.IdentityModel.Tokens.Jwt" assembly of version 3.0.2.
From the package manager console, try:
Update-Package System.IdentityModel.Tokens.Jwt -Version 3.0.2