ASP.Net Bearer Authentication using JWT - c#

I have implemented the normal login behavior where a JWT token is issued upon successful logon. The API (ASP.Net) should then restrict access to certain Controller actions using the [Authorize] attribute - however I keep on getting a:
401 - Unauthorized response ("Authorization has been denied for this request.")
I am relatively sure I am not configuring the JWT Token authentication correctly - however I have this in my startup, using Microsoft.Owin.Host.SystemWeb:
public void Configuration(IAppBuilder app) {
ConfigureOAuthTokenConsumption(app);
}
private void ConfigureOAuthTokenConsumption(IAppBuilder app) {
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions {
AllowedAudiences = new string[] { "*" },
TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = "SomeValidIssuer",
ValidateAudience = true,
ValidAudience = "SomeValidAudience",
ValidateLifetime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("mysupersecretkey"))
}
}
);
}
With a very simple token being passed through to the request via header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MzM1NzY5MDEsImlzcyI6IlNvbWVWYWxpZElzc3VlciIsImF1ZCI6IlNvbWVWYWxpZEF1ZGllbmNlIn0.YfgbVuLgjTagkZTHwXRh9gQxOq5boeMa7TOgq0keKc0
You can use this site to see the contents of the token
I have followed this excellent link on Securing your ASP.NET Core 2.0 API which is working like a charm, but dotNetCore is not an option for the project I am currently working on - however I have followed a similar path in issuing the tokens.
What am I doing wrong, why am I getting a 401 on my request?

Related

JWT token authorization on the c# server side not clear

I have a github project link where the client sends Authorization header to the server containing the JWT token like below. What I can't understand is how on the server side [Authorize(Role.Admin)]can understand the Role.Admin which is application specific enum value (belonging to the account object - details beow).
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add auth header with jwt if account is logged in and request is to the api url
const account = this.accountService.accountValue;
const isLoggedIn = account && account.jwtToken;
const isApiUrl = request.url.startsWith(environment.apiUrl);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: { Authorization: `Bearer ${account.jwtToken}` }
});
}
return next.handle(request);
}
On the server side I have middleware class containing the code that decripts the token like this:
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);
var jwtToken = (JwtSecurityToken)validatedToken;
var accountId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);
// attach account to context on successful jwt validation
context.Items["Account"] = await dataContext.Accounts.FindAsync(accountId);
}
catch
{
// do nothing if jwt validation fails
// account is not attached to context so request won't have access to secure routes
Console.WriteLine("Failed");
}
So on the server side I have controller that has [Authorize(Role.Admin)] annotation like below which I can't understand - how Authorize can understand the Role:Admin - which is application specific - is it via introspection?
[Authorize(Role.Admin)]
[HttpGet("all-dates")]
public ActionResult<ScheduleDateTimeResponse> GetAllDates()
{
var dates = _accountService.GetAllDates();
return Ok(dates);
}
The project has a custom AuthorizeAttribute for which the Role enumeration is in scope. This differs from the standard attribute from the BCL. This attribute implements IAuthorizationFilter which the asp.net middleware understands must be invoked against this controller action.
Probably the application is referring to some kind of authentication service before the call endpoint. You should be able to see it in the console logs.
Check in solution nugets if there is no some kind of company aut service package
Ofc if you are sure that role is not a part of JWT token.
you can check that with: https://jwt.io/
Sorry...but I cannot add just simply comment yet...

Asp.Net Core 2 Validating bearer token

I am struggling to find an exact way of validating my OAuth bearer token which is passed when a request is sent to the API am working on which is a Asp.Net core project.
The purpose here is to extract the bearer token and Validate it and if all is fine then continue with the request.
So far my findings have come across the following
JWT bear token authorization which mostly talks about access_token
Asp.Net core security middleware
Custom Authorize attribute which handle this.
I am not really sure how I can achieve my validation? Should I extract the bearer token and then create a custom validating method?
Ideally would like the [Authorize] attribute to handle this.
Suggestions please?
Well finally after more research I finally found that custom AuthorizationHandler is a more suitable solution as suppose to using custom Authorize attributes which is not suggested in Asp.Net Core.
It was simple to setup and I am able to extract my Bearer token from the header for further authorization with OAuth.
Here is a my approach:
public class CustomAuthorizationHandler: IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
if (authHeader != null && authHeader.Contains("Bearer"))
{
var token = authHeader.Replace("Bearer", "");
// Now token can be used for further authorization
}
throw new NotImplementedException();
}
}
Lastly registering the handler in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>();
}
I think to put the following code snippet inside ConfigureServices() should be able to validate your access_token after installing Microsoft.AspNetCore.Authentication.JwtBearer NuGet package:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
//options.SaveToken = true;
options.MetadataAddress = ValidationEndPoint;
options.RequireHttpsMetadata = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidateIssuer = true,
ValidateAudience = true,
ValidIssuer = tokenIssuer,
ValidAudiences = new[] { clientId },
ValidAudience = null
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("MyPolicy", policy =>
{
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
});
});
remember to put app.UseAuthentication() and app.UseAuthorization() in the Configure() method. And add [authorize] to your controller API.

IdentityServer4 token signing validation

I have IdentityServer4 that generates signed JWT tokens.
In my web api I added auth middleware to validate these tokens:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = env.IsProduction() ? "https://www.example.com/api/" : "http://localhost/api/",
AllowedScopes = { "WebAPI", "firm",
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile },
RequireHttpsMetadata = env.IsProduction(),
});
It works perfectly. However, I suspect it doesn't verify signature of jwt token because there is no public key configured to validate token. How to configure token signature validation?
PS: I try to use UseJwtBearerAuthentication instead this way:
var cert = new X509Certificate2("X509.pfx", "mypassword");
var TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidIssuer = env.IsProduction() ? "https://www.example.com/api/" : "http://localhost/api/",
IssuerSigningKey = new X509SecurityKey(cert),
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
Authority = env.IsProduction() ? "https://www.wigwam3d.com/api/" : "http://localhost/api/",
Audience = "WebAPI",
RequireHttpsMetadata = env.IsProduction(),
TokenValidationParameters = TokenValidationParameters
});
It also works (and I hope validates token signature also!) but gives me another bug:
UserManager.GetUserAsync(HttpContext.HttpContext.User)
return null, while using UseIdentityServerAuthentication returns me correct User
I think there is no need to add certificate to you API for validation. .UseIdentityServerAuthentication() middleware calls your IdentiyServer to retrieve public key on startup from https://www.example.com/api/.well-known/openid-configuration. At least that's my understanding how it works.
Finally I done it with JwtBearerAuthentication,
GetUserAsync function failure can be fixed with call to:
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
because of this issue: https://github.com/aspnet/Security/issues/1043
Any ideas to configure the same using IdentityServer auth are welcome!

ASP.Net WebApi responding unauthorized acess

I am having ASP.Net webapi which is hosted on Azure. Actually I am having two different subscription
For Azure Active Directory - say its demo.onmicrosoft.com
For hosting webapi. - say its hosted on abc.azurewebsites.net
My api which is hosted on abc.azurewebsites.net is registered on demo.onmicrosoft.com. My client app is a service app which is authenticating against a user that resides in demo.onmicrosoft.com. Authentication is basic authentication by passing user credentials and recieveing AccessToken from Azure Active Directory. After recieving token from demo.onmicrosoft.com I am calling api from abc.azurewebsites.net. Like this:
https://abc.azurewebsites.net/api/some/queryapi
Now, In my controller if I use
[Authorize]
public class SomeController:ApiController
{
//my code
}
I am getting uauthorized access. and if I remove that attribute from controller it works fine.
Can you help me out.Even after app registration & authentication why I am getting so. Is that because of two different subscription or something else.
Update
I am sending token to my webapi this way
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
}
IMO, since my token is from azure ad of some other subscription so its not getting recognized by my webapi.
More Update - Startup.cs
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
Web.config
<add key="ida:Tenant" value="demo.onmicrosoft.com" />
<add key="ida:Audience" value="https://demo.onmicrosoft.com/4e4xxxx5-5xx1-4355-8xxc-705xxxx163" />
<add key="ida:ClientID" value="d0xxxxa-2xxx6-4xx-9e58-07xxxxxxxx1" />
It is relative to how you protect the web API instead of Azure subscription. For example, here is a piece of code which used to protect web API using Azure AD in the .net core:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
// Add the console logger.
loggerFactory.AddConsole(LogLevel.Debug);
// Configure the app to use Jwt Bearer Authentication
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]),
Audience = Configuration["AzureAd:Audience"],
Events = new JwtBearerEvents
{
OnTokenValidated = tokenValidated ,
OnAuthenticationFailed= AuthenticationFailed
}
});
}
This web API will verify the signature of the token to ensure the token is issued from Azure AD. Then it will check the aud claim in the access token. If the aud claim also matched as we config in above code the we can call the web API successfully.
1). Make sure Azure Authentication setting off.
2). In StartupAuth.cs
PublicClientId = "self";
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString("/Token"),
Provider = new ApplicationOAuthProvider(PublicClientId),
AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
// Note: Remove the following line before you deploy to production:
AllowInsecureHttp = true
};
// Enable the application to use bearer tokens to authenticate users
app.UseOAuthBearerTokens(OAuthOptions);
follow this article: https://www.asp.net/web-api/overview/security/individual-accounts-in-web-api

How to tell IdentityServer 4 middleware not to authenticate if earlier middleware has already done so?

My ASP.NET Core REST API uses 2 authentication middleware - JWT to authenticate users (tokens issued by ADFS), and IdentityServer to authenticate calls from other services. The configuration for those middleware looks like this:
JWT
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new X509SecurityKey(federationMetadata.GetSigningCertificate()),
ValidateIssuer = true,
ValidIssuer = federationMetadata.GetIssuerUri().AbsoluteUri,
ValidateAudience = true,
ValidAudience = validAudience,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
app.UseJwtBearerAuthentication(new JwtBearerOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true,
TokenValidationParameters = tokenValidationParameters
});
Identity Server 4 (IDS)
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = dbConfig.IdentityServer.AbsoluteUri,
ScopeName = Configuration.ServiceName,
RequireHttpsMetadata = false
}
I am having problems with this setup:
When a user passes a token from ADFS, the JWT middleware successfully validates the token. The IDS middleware then cannot validate the token and throws an error:
IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey , KeyId: <MyRsaKey>
This is expected. How can I tell the IDS middleware not to authenticate if an earlier middleware has successfully authenticated the request?
I've tried a couple of things. It seems to me that the ideal would be to work out on the IDS OnMessageReceived event that the user has been authenticated, and tell the IDS middleware to do no further checking:
JwtBearerEvents = new JwtBearerEvents()
{
OnMessageReceived = async (context) =>
{
if(UserIsAlreadyAuthenticated)
DoNothingMore();
}
}
How could I test if the user is authenticated?
How do I tell the middleware to accept the authentication and do no more processing?
I have tried trapping the IDS OnAuthenticationFailed event and skipping the middleware:
OnAuthenticationFailed = async (context) =>
{
if (context.Exception is IdentityServerNotAvailableException)
{
context.SkipToNextMiddleware();
}
}
but I then get a scope validation failure:
Scope validation failed. Return 403.
Where is the scope validation happening? Because it seems that any error in the IDS middleware results in a scope validation error, whether I SkipToNextMiddleware or not.
If I remove the IDS middleware, everything works.
Thanks for any help!
EDIT: I have solved the scope validation issue for now. I tried setting the ValidateScope = false option on the IDS middleware, but this causes a null reference exception inside the middleware; but I've found that if I comment out the ScopeName = Configuration.ServiceName option, the scope validation issue is solved (I handle it in a custom handler - not ideal).
Still need to know: Either how to tell the IdentityServer middleware to skip if the token has been authenticated successfully, or to identify successful previous authentication in the OnMessageReceived event so I can call SkipToNextMiddleware() there myself.
Thanks!

Categories

Resources