IdentityServer4 - protecting WebAPI actions - .net-core

I've successfully integrated my .net Core WebAPI with IdentityServer4 where a client is able to request a token and send back via auth. header when calling the controller and able to successfully authorize.
However, I do have a question around fined grained authorization.
When inserting a new ApiResource, I am also adding scopes (ex. app.api1.read, app.api2.read, etc..) and the client has access to the scopes (app.api1.read and app.api2.read). When the client calls app.api1, they can pass either scopes app.ap1.read or app.api2.read and both would be authorized successfully. Ideally, I would want the client to pass scope for api1 (ex. app.api1.read) and if they pass scope for api2 it should not be authorized.
Startup.cs
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
// base-address of your identityserver
options.Authority = "https://localhost:44393";
options.RequireHttpsMetadata = false;
});
TestController.cs
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[Route("api/[controller]")]
[ApiController]
public class TestController : Controller
{
Your assistance is much appreciated.

Related

.net core B2C / 3rd party JWT validation and authorization

.net core web api v3.1
Using Azure B2C to authenticate users and have some .net core 3.1 APIs that are outside of that direct workflow. Authenticated services will be calling my endpoints. Almost all of this is server to server.
I need to verify the token, claims, expiration etc. How can I best do that? Can I use the token alone to "authenticate" the user / caller within my .net core API? Can I protect my endpoints with the [Authorize] attribute? I am hoping to avoid the need to connect directly with Azure B2C but rather just rely on the token.
The JWT / .net core APIs examples that I find all include clients authenticating directly with B2C. Any suggestions on how to accomplish this?
In my scenario an user interactively authenticates in an app against a third-party using OAuth2 and retrieves a JWT.
The app then requests an ASP.NET Core Web API (.net Core 3.1) by using this token. The API controllers have the [Authorize] Attribute and the token is checked using Azure B2C authentication.
I don't know if this exactly meets your scenario. But since it took me quite some time to get this running, I thought it might give you a hint.
This is how I set up the JWT-authentication.
// In the startup.cs:
public void ConfigureServices(IServiceCollection services)
{
string clientId = "<guid-identifying-the-client>"
string policy = "B2C_1A_Sample_Policy"
string tenant = "sampletenant.onmicrosoft.com"
string aadInstance = $"https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration?p={policy}"
services.AddAuthentication(opt =>
{
opt.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(jwtOpt =>
{
jwtOpt.TokenValidationParameters = new TokenValidationParameters
{
ValidAudience = clientId,
AuthenticationType = policy
};
jwtOpt.ConfigurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(
aadInstance,
new OpenIdConnectConfigurationRetriever())
{
RefreshInterval = new TimeSpan(0, 5, 0)
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//...
app.UseAuthentication();
app.UseAuthorization();
//...
}
Note: The aad instance used in the sample above is deprecated and needs to be migrated to b2clogin.com until December 4th, 2020

Call REST API Client from another API with JWT token

I have a platform with two API's. Let's call them API A and API B. I have a case, that i have to call API B from API A and both need a singned user. So i added there Policies and JWT token.
Of course, Policies are the same in both APIs.
So, using swagger i am calling Method A in API A, with authorized Token. Method is working correctly and calling Method B from API B, but HTTP client hasn't got token, because is not filling anywhere..
I am trying to fill in a Startup.cs in API A, a httpClient to using the token, but i dont know, how to get HttpContext with the token.
This is how i am trying to fill the token in the HttpClient, but i dont exactly now, how i should get the token from the context.
services.AddHttpClient("Api_B_Client", x =>
{
x.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "");
});
In the context token exists, because i am parsing like this:
services.AddAuthentication().AddJwtBearer(cfg =>
{
SetupJwtBearerWithDecryption(cfg);
});
In the SetupJwtBearerWithDecryption method i have an OnMessageReceived event and there i am parsing the token
My solution for the problem is to get from Configuration HttpContextAccessor and the object has authorization token which i was looking for
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddHttpClient("client", x =>
{
var context = Configuration.Get<HttpContextAccessor>();
x.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", context.HttpContext.Request.Headers["Authorization"]);
});

check user validation in Asp.net core with jwt authorization

I implemented Microsoft Identity and JWT in my web api,
a client can login and get a JWT token and store it in the application.
since the expiration of the token the user can access the the server,
but if I remove a user from my database, the removed user still has its token and can access the web api,
how can I check the validation of the user?
One option is to validate the current user on the JwtBearerEvent OnTokenValidated event which will be triggered after every successful authentication
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var userService = ServiceProvider.GetService<IUserService>();
if(userService.IsUserRemoved(context.Principal.Identity.Name))
context.Fail("User is removed");
return Task.CompletedTask;
}
};
});
Note: In this example I use ServiceProvider, to get the an instance of IUserService, which is stored in the Startup.cs class as a parameter. Initialized as ServiceProvider = services.BuildServiceProvider(); in the ConfigureServices method. The IUserService is a wrapper class where you need to implement the IsUserRemoved method which will operate on your user provider implementation.
Another option is to implement and register your own SecurityTokenValidator. To do so you need to create a class implemented ISecurityTokenValidator interface:
//using Microsoft.IdentityModel.Tokens
public class CustomValidator : ISecurityTokenValidator
{
//interface implementation
...
}
and register it as an additional token validator via JwtBearerOptions.SecurityTokenValidators property:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer( options => {
options.SecurityTokenValidators.Add(new CustomValidator())
});

Replacing Cookie by Token based authentication in ASP.NET OWIN OpenIdConnect code authorization flow

We have a web application written in ASP.NET that uses MVC for serving our Single Page Applications and Web API for ajax calls.
The authentication uses Microsoft.Owin and OpenIdConnect with Azure AD for Authority. The OAUTH flow is server side code authorization.
Then in Startup.Auth.cs we have
public void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
var cookieAuthenticationOptions = new CookieAuthenticationOptions()
{
CookieName = CookieName,
ExpireTimeSpan = TimeSpan.FromDays(30),
AuthenticationType = CookieAuthenticationDefaults.AuthenticationType,
SlidingExpiration = true,
};
app.UseCookieAuthentication(cookieAuthenticationOptions);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
AuthorizationCodeReceived = (context) =>
{
/*exchange authorization code for a token
stored on database to access API registered on AzureAD (using ADAL.NET) */
},
RedirectToIdentityProvider = (RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context) =>
{
/* Set the redirects URI here*/
},
});
}
When clicking on signin we navigate to an url whose routes map to the methods of the following MVC controller
public class AccountController : Controller
{
public void SignIn(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext()
.Authentication.Challenge(authenticationProperties, OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
public void SignOut(string signalrRef)
{
var authenticationProperties = /* Proper auth properties, redirect etc.*/
HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType);
}
Then the end-user connected to our application is authenticated between our client apps and the ASP.net server using an ASP.NET cookie. We would like to use Token Based approach instead. If you are interested this is the reason.
I tried to replace
the Nuget package Microsoft.Owin.Security.Cookies by Microsoft.Owin.Security.OAuth and in Startup.cs
replacing
app.UseCookieAuthentication(cookieAuthenticationOptions); by app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
and in my AccountController we changed the challenge from HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, CookieAuthenticationDefaults.AuthenticationType); to HttpContext.GetOwinContext().Authentication.SignOut(authenticationProperties,
OpenIdConnectAuthenticationDefaults.AuthenticationType, OAuthDefaults.AuthenticationType);
The problem is that with Cookie the set-cookie was automatically sent in web request respond when the flow completes while redirecting to the url we specified.
Where can I find the Bearer generated by OWIN with UseOAuthBearerAuthentication (if there is any) **, **Where and When should I send it back to my client SPAs
Note: an open source sample of what we are trying to do can be found in this github repository.
I think there are two approaches for you to consider.
Use javascript libraries to perform sign-in & token acquisition within your single page app. Then your backend is purely an web API, and can just use OAuth bearer middleware to authenticate requests. The backend doesn't know anything about signing the user in. We have a good sample that takes this approach here. If your backend needs to make API calls as well, you could consider the OnBehalfOf flow as well. I usually recommend this approach.
Use the OpenIDConnect middleware in your server to perform user sign-in and token acquisition. You might even be able to omit the usage of the CookieAuthenticationMiddleware entirely (although I'm not 100% sure). You can capture the token in the AuthorizationCodeReceived notification as you mention, and you could redirect back to your SPA with the token in the fragment of the URL. You could also have some route which delivers the tokens (which are cached on your server) down to your javascript. In either case, you'll need to ensure that an outside caller can't get access to your tokens.
The thing to keep in mind will be how you refresh tokens when they expire. If you use #1, most of it will be handled for you by libraries. If you use #2, you'll have to manage it more yourself.

ASP.NET API returns Authorization has been denied for this request on localhost but works normally on Azure

[Authorize]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "Simple" , "Test"};
}
}
This is a simple "Web API 2" app.
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = true,
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
As shown the application is using the WindowsAzureActive Directory authentication and Authorization.
Note that it works normally when published to Azure Api App, but always denies the request when in localhost.
I am not sure what happened, it used to work before.
Regards
Normally this is due to a mismatch in the audience you expect in the web API (the value you set via ValidAudience) and what you get in the incoming token. The value in the token reflects the resource identifier you used when requesting the token from the client.
Do you change the client code to request a different audience when calling the localhost instance vs the Azure API one?
Also, how do you publish the API to Azure? If you use VS, and in the Publish wizard settings you have the checkbox "use organizational auth" checked, the deployed web API will have a different audience value in its web.config.

Resources