How to do Role-based Web API Authorization using Identity Server 4 (JWT) - asp.net-web-api

This is all new to me and I'm still trying to wrap my head around it. I've got an IDP (Identity Server 4) set up, and I was able to configure a client to authenticate to it (Angular 6 App), and further more to authenticate to an API (Asp.Net Core 2.0). It all seems to work fine.
Here's the client definition in the IDP:
new Client
{
ClientId = "ZooClient",
ClientName = "Zoo Client",
AllowedGrantTypes = GrantTypes.Implicit,
AllowAccessTokensViaBrowser = true,
RequireConsent = true,
RedirectUris = { "http://localhost:4200/home" },
PostLogoutRedirectUris = { "http://localhost:4200/home" },
AllowedCorsOrigins = { "http://localhost:4200" },
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
IdentityServerConstants.StandardScopes.Phone,
IdentityServerConstants.StandardScopes.Address,
"roles",
"ZooWebAPI"
}
}
I'm requesting the following scopes in the client:
'openid profile email roles ZooWebAPI'
The WebAPI is set up as such:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonFormatters()
.AddAuthorization();
services.AddCors();
services.AddDistributedMemoryCache();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "https://localhost:44317";
options.RequireHttpsMetadata = false;
options.ApiName = "ZooWebAPI";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors(policy =>
{
policy.WithOrigins("http://localhost:4200");
policy.AllowAnyHeader();
policy.AllowAnyMethod();
policy.AllowCredentials();
policy.WithExposedHeaders("WWW-Authenticate");
});
app.UseAuthentication();
app.UseMvc();
}
By using [Authorize] I was successfully able to secure the API:
[Route("api/[controller]")]
[Authorize]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public ActionResult Get()
{
return new JsonResult(User.Claims.Select(
c => new { c.Type, c.Value }));
}
}
Everything works fine, if client is not authenticated, browser goes to IDP, requires authentication, redirects back with access token, access token is then used for API calls that are successfully made.
If I look at the Claims in the User object, I can see some information, but I don't have any user information. I can see the scopes, and etc, but no roles for example. From what I read, that is to be expected, and the API should not care about what user is calling it, but how would I go by restricting API calls based on roles? Or would that be completely against specs?
The IDP has an userinfo end point that returns all the user information, and I thought that would be used in the WebAPI, but again, from some reading, it looks like the intention is for that end point to be called from the client only.
Anyway, I would like to restrict Web API calls based on the roles for a specific user. Does anyone have any suggestions, comments? Also, I would like to know what user is making the call, how would I go by doing that?
JWT example:
Thanks

From what I can learn from your information, I can tell the following.
You are logging in through an external provider: Windows Authentication.
You are defining some scopes to pass something to the token that indicates access to specific resources.
The User object you speak of, is the User class that gets filled in from the access token. Since the access token by default doesn't include user profile claims, you don't have them on the User object. This is different from using Windows Authentication directly where the username is provided on the User Principle.
You need to take additional action to provide authorization based on the user logging in.
There a couple of points where you can add authorization logic:
You could define claims on the custom scopes you define in the configuration of Identityserver. This is not desirable IMHO because it's fixed to the login method and not the user logging in.
You could use ClaimsTransformation ( see links below). This allows you to add claims to the list of claims availible at the start of your methods. This has the drawback ( for some people an positive) that those extra claims are not added to the access token itself, it's only on your back-end where the token is evaluated that these claims will be added before the request is handled by your code.
How you retrieve those claims is up to your bussiness requirements.
If you need to have the user information, you have to call the userinfo endpoint of Identityserver to know the username at least. That is what that endpoint is intended for. Based on that you can then use your own logic to determine the 'Roles' this user has.
For instance we created an separate service that can configure and return 'Roles' claims based upon the user and the scopes included in the accesstoken.
UseClaimsTransformation .NET Core
UseClaimsTransformation .NET Full framework

Related

Azure AD B2C & Microsoft Identity Web - Sign In with multiple policies (.net Core 3.1)

I have an application using .NET Core 3.1 MVC Web App that uses Azure AD B2C to sign in users and I've just migrated it to use Microsoft Identity Web library.
We want to have two different policies for Sign In, one for regular users (B2C_1A_SignUpOrSignIn) and one for admin users (B2C_1A_SignInAdmin).
So, in the Appsettings, we have the following format:
"AzureAdB2C": {
"Instance": "https://url.b2clogin.com/tfp/",
"ClientId": "clientId",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout/B2C_1A_SignUpOrSignIn",
"Domain": "url.onmicrosoft.com",
"Domain_b2cLogin": "url", // Required by the Cookie Policy
"SignUpSignInPolicyId": "B2C_1A_SignUpOrSignIn",
"SignInAdminPolicyId": "B2C_1A_SignInAdmin",
"ResetPasswordPolicyId": "B2C_1A_PasswordReset",
"EditProfilePolicyId": "",
"ClientSecret": key,
"B2cExtensionAppClientId": "key"
},
In the Startup class, I just added the following:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration, "AzureAdB2C");
And I also overrode the "AzureController", so, for a regular user, I use the same method obtained from
here Microsoft Identity Web - Account Controller "SignIn". However, for an admin user, I changed that method to use something similar to what is provided by the PasswordReset method, as the following:
public IActionResult SignInAdmin()
{
string scheme = OpenIdConnectDefaults.AuthenticationScheme;
var redirectUrl = Url.Content("~/");
var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
properties.Items[Constants.Policy] = "B2C_1A_SignInAdmin";
return Challenge(properties, scheme);
}
So, as you can see, I'm using a different policy name for this method.
Everything seems to work fine, the user is redirected to the correct login page based on the policy and the token is issued by Azure and our application accepts the Token, in our method
options.Events.OnTokenValidated = context => {}
However, soon after that, something goes wrong with the authentication and the method
options.Events.OnRemoteFailure
is called with the following exception
"{"Message contains error: 'invalid_grant', error_description: 'AADB2C90088: The provided grant has not been issued for this endpoint. Actual Value : B2C_1A_SignUpOrSignIn and Expected Value : B2C_1A_SignInAdmin ..."
So, my question is, what do I have to do to be able to use two different policies to sign in? Or is there any configuration that I should do to be able to do that?
Thank you in advanced.
I guess the B2c Middleware validates the "tfp" claim. This should usually match the SignInPolicyId. You might have to override the TokenValidation to let both policies (tfp = trustedFrameworkPolicy) be valid

WebAPI scope authorization through azure app registrations

I am trying to control authorization via app registrations in Azure.
Right now, I have two app registrations set up.
ApiApp
ClientApp
ApiApp is set up with the default settings, but I have added this to the manifest:
"oauth2Permissions": [
{
"adminConsentDescription": "Allow admin access to ApiApp",
"adminConsentDisplayName": "Admin",
"id": "<guid>",
"isEnabled": true,
"type": "User",
"userConsentDescription": "Allow admin access to ApiApp",
"userConsentDisplayName": "Admin",
"value": "Admin"
},
...
]
In the client app registration, I have all the defaults, but I added:
In the keys, a password for authenticating the app against AD
In required permissions, I added ApiApp and required the delegated permission "Admin." I saved that, clicked done, then I clicked "Grant Permissions" to make sure the permissions had a forced update.
In my client app, it uses this code for authentication purposes:
...
var context = new AuthenticationContext(authority);
var clientCredentials = new ClientCredential(<clientId>, <clientSecret>);
var result = await context.AcquireTokenAsync(<apiAppUri>, clientCredentials);
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
var webResult = await client.GetAsync(<api uri>);
My ApiApp is just using the built in authorization if you select work or school accounts when you create a Web API project:
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
TokenValidationParameters = new TokenValidationParameters {
ValidAudience = ConfigurationManager.AppSettings["ida:Audience"]
},
});
}
This works:
[Authorize]
public class ValuesController : ApiController
These do not work:
[Authorize(Users = "Admin")]
public class ValuesController : ApiController
or
[Authorize(Roles= "Admin")]
public class ValuesController : ApiController
Based on what I'm reading, I believe I have everything set up appropriately except the ApiApp project itself. I think I need to set up the authorization differently or with extra info to allow the oauth2Permission scopes to be used correctly for WebAPI.
What step(s) am I missing to allow specific scopes in WebAPI instead of just the [Authorize] attribute?
I used Integrating applications with Azure Active Directory to help me set up the app registrations, along with Service to service calls using client credentials , but I can't seem to find exactly what I need to implement the code in the Web API part.
UPDATE
I found this resource: Azure AD .NET Web API getting started
It shows that you can use this code to check out scope claims:
public IEnumerable<TodoItem> Get()
{
// user_impersonation is the default permission exposed by applications in Azure AD
if (ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope")
.Value != "user_impersonation")
{
throw new HttpResponseException(new HttpResponseMessage {
StatusCode = HttpStatusCode.Unauthorized,
ReasonPhrase = "The Scope claim does not contain 'user_impersonation' or scope claim not found"
});
}
...
}
However, the claims I get do not include any scope claims.
You have to use appRoles for applications, and scopes for applications acting on behalf of users.
As per GianlucaBertelli in the comments of Azure AD, Scope-based authorization:
...in the Service 2 Service scenario, using the client credentials flow you won’t get the SCP field. As you are not impersonating any user, but the calling App (you are providing a fixed credential set).
In this case you need to use AppRoles (so Application permissions, not delegated) that results in a different claim. Check a great how-to and explanation here: https://joonasw.net/view/defining-permissions-and-roles-in-aad.
In the link he provides, it discusses appRoles in the application manifest.
The intention behind the resources I was looking at before was to allow users to login to the client application, and then the client application authenticates against the API on behalf of the user. This is not the functionality I was trying to use -- simply for a client application to be able to authenticate and be authorized for the API.
To accomplish that, you have to use appRoles, which look like this in the application manifest:
{
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "Read all todo items",
"id": "f8d39977-e31e-460b-b92c-9bef51d14f98",
"isEnabled": true,
"description": "Allow the application to read all todo items as itself.",
"value": "Todo.Read.All"
}
]
}
When you set the required permissions for the client application, you choose application permissions instead of delegated permissions.
After requiring the permissions, make sure to click the "Grant Permissions" button. To grant application permissions, it requires an Azure Active Directory admin.
Once this is done, requesting an access token as the client application will give you a "roles" claim in the token, which will be a collection of string values indicating which roles the application holds.

Running IdentityServer4 and API and a Web App from a single 'site'

We have a legacy system written in Core MVC 1, using IdentityServer4 for API access and Identity for user management. The site includes a set of API controllers as well, which a mobile application connects to for authentication and data.
We have been having general stability issues, which we have not been able to get to the bottom of. We have decided to upgrade the system to the latest version of MVC Core and in the process IdentityServer4 requires an upgrade.
The problem is that the authentication pipeline has changed dramatically between versions (Core MVC 1 - 2 and Identity 1 - 2) and we are unable to determine a configuration that works.
In short we need:
Cookie Authentication for web site access
OAuth 2 password grant flow for app access
However, despite this setup working on the legacy version, it does not seem to want to play ball on the newer setup. It seems we can have one or the other, but not both. There doesn't appear to be any example projects available anywhere that demonstrate such a setup.
I understand this setup is not ideal in that these systems should be split out, and I am going to be making a recommendation as such. I have seen hints of routing api requests through a pipeline setup for Bearer authentication using MapFrom but haven't managed to determine a working setup.
UPDATE: Startup.cs
services.AddIdentity<ApplicationUser, IdentityRole>(o =>
{
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireUppercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = 6;
})
.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
var AuthServerConfig = new IdentityServerConfig(Configuration.GetSection("IdentityServer"));
var IdentityCert = AuthServerConfig.GetCerttificate();
var IdentityConfig = services.AddIdentityServer()
.AddInMemoryIdentityResources(AuthServerConfig.GetIdentityResources())
.AddInMemoryApiResources(AuthServerConfig.GetApiResources())
.AddInMemoryClients(AuthServerConfig.GetClients())
.AddAspNetIdentity<ApplicationUser>();
//to secure the API
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = AuthServerConfig.Settings.RootUrl;
options.RequireHttpsMetadata = false;
options.ApiName = AuthServerConfig.Settings.Scope;
});
And in the Configure Method we have:
app.UseIdentityServer();
app.UseAuthentication();
The stage we are at now is that IdentityServer seems to be operational in that a token can be requested. You can call into an API endpoint and gain access so long as that endpoint had the following attribute:
[Authorize(AuthenticationSchemes = "Bearer")]
However, we want the API to be authenticated using both Identity Cookies as well as Bearer tokens, as there is a swagger UI for querying the API when logged in.
Using just the [Authorize] attribute will allow it to be accessed via cookies, but not access tokens through Postman (401)
Not sure if this is solved but what i personally done to enable API to be authenticated via cookie and bearer token is implement both schemes:
e.g.
services.AddAuthentication()
.AddCookie(options => {
options.LoginPath = "/Account/Unauthorized/";
options.AccessDeniedPath = "/Account/Forbidden/";
})
.AddJwtBearer(options => {
options.Audience = "http://localhost:5001/";
options.Authority = "http://localhost:5000/";
});
In the api controller i use [Authorize(AuthenticationSchemes = AuthSchemes)]
[Authorize(AuthenticationSchemes = AuthSchemes)]
public class TodoController: Controller
More details on different schemes and usages of them you can have a look here:
https://learn.microsoft.com/en-us/aspnet/core/security/authorization/limitingidentitybyscheme?tabs=aspnetcore2x
Hope that helps.

What is a good way to pass additional information to the http response when issuing access token with Owin middleware in Asp Net Web Api?

I am using Owin middleware to implement token-based security for my application. When issuing the access token to the client I would also like to pass User Id along with the token, so that, the client application will know the User Id and will be able to call GetUserById (one of the methods inside UserController) in order to show the user his starting page. The best solution I could come up with so far is just adding User Id to the response header. Take a look at my OAuthAuthorizationServerProvider class, in GrantResourceOwnerCredentialsmethod I am adding User Id to the header, using context.Response.Headers.Add("User-Id", new string[] { "1" })
Here is the implementation of my OAuthAuthorizationServerProviderclass
public class AuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
context.Validated();
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
var identity = new ClaimsIdentity(context.Options.AuthenticationType);
//The actual credential check will be added later
if (context.UserName=="user" && context.Password=="user")
{
identity.AddClaim(new Claim("Id", "1"));
context.Validated(identity);
//Here I am adding User Id to the response header
context.Response.Headers.Add("User-Id", new string[] { "1" });
}
else
{
context.SetError("invalid_grant","The credentials provided are not valid");
return;
}
}
}
Basically the client then will have to read User-Id from the header. Is this a good solution I came up with or there is a better one? Also what if I want to pass the whole User object with all its properties to the response is it possible and how to do this?
Since you store the ID already in the claims, why don't you just decode your token on the client and read out the user id like that? Passing it through the headers could allow tampering with it (security).
Have a look on how you could achieve to decode your token and read the claims. This is a c# example https://contos.io/peeking-inside-your-jwt-tokens-using-c-bf6a729d06c8 but this could also be done even through javascript.
This, assuming you use the JWT-format as token (was not specified in your initial question).
Bad way to store UserID as a response header back to client side. This is a huge security concern.
The best way would be to store it as a Claims.
It is very easy to achieve this and get back the claims in the client side.
In your controller, call this and remember to reference
using Microsoft.AspNet.Identity;
var userId = User.Identity.GetUserId();

Custom session not working with Authenticate

I'm trying to design a solution where a ServiceStack server can just use an authentication cookie from ASP.NET. (In reality, it could be any cookie. It's just getting a session ID that it can lookup details using a back channel). The custom auth providers don't seem to be the right direction since they are based on credentials being sent. Instead, a GlobalRequestFilter made more sense to me. In there, I check the cookie, get the external session information, then set them to the ServiceStack session and set IsAuthenticated. This works fine in the request service as it has access to the session details that it needs. Fine so far.
The issue, is that when I decide to lock down services with the Authenticate attribute, it apparently runs the attribute prior to my filter so it always wants to redirect them to login. What is the recommended place to add my logic so it fires before the Authenticate attribute and validates properly?
ServiceStack's [Autenticate] attribute is for use with ServiceStack's AuthProvider model so you'll still want to use a Custom AuthProvider. You can have a look at the IAuthWithRequest Auth Providers in the last release notes for examples of creating Custom Auth Providers that aren't based on using credentials:
JwtAuthProviderReader.cs
ApiKeyAuthProvider.cs
AspNetWindowsAuthProvider.cs
By implementing IAuthWithRequest interface in your AuthProvider the [Authenticate] Request Filter will call PreAuthenticate() to perform any Auth validation before validating whether the User is Authenticated or not. Here you can populate the Users Session if the User is Authenticated, e.g:
public class MyAuthProvider : AuthProvider, IAuthWithRequest
{
public override bool IsAuthorized(IAuthSession session, IAuthTokens tokens, Authenticate request = null)
{
return session.IsAuthenticated;
}
public override object Authenticate(IServiceBase authService, IAuthSession session, Authenticate request)
{
throw new NotImplementedException("Authenticate() should not be called directly");
}
public void PreAuthenticate(IRequest req, IResponse res)
{
//Do any Auth validation...
//populate the Session in the Request to Authenticate this user
req.Items[Keywords.Session] = new AuthUserSession {
UserName = ...,
Email = ...,
//populate other fields
IsAuthenticated = true,
};
}
}
Then to register your custom Auth Provider add it to your AuthFeature plugin in AppHost.Configure(), e.g:
Plugins.Add(new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new MyAuthProvider (),
}));

Resources