WebAPI scope authorization through azure app registrations - asp.net-web-api

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.

Related

"name claim is missing" with AspNetZero and Okta

I'm trying to use Okta SSO to authenticate my users in AspNetZero app.
When I click on the OpenID in the bottom of the login page, I'm redirected to Okta login and then I'm back to my app login page and finally "openIdConnectLoginCallback" is calling "ExternalAuthenticate" on the TokenAuthController and the call to _externalAuthManager.GetUserInfo throw exception "name claim is missing".
In "login.service.ts", claims array contain "name" claim and ExternalAuthenticateModel.ProviderAccessCode contains a valid JSon Token and I check and "name" is in the token.
let claims = this.oauthService.getIdentityClaims();
const model = new ExternalAuthenticateModel();
model.authProvider = ExternalLoginProvider.OPENID;
model.providerAccessCode = this.oauthService.getIdToken();
model.providerKey = claims['sub'];
model.singleSignIn = UrlHelper.getSingleSignIn();
model.returnUrl = UrlHelper.getReturnUrl();
Here is my appsettings.json
Here is my app configuration in Okta
Any ideas to fix that "name" claim missing ?

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

How to do Role-based Web API Authorization using Identity Server 4 (JWT)

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

IdentityServer 3: How to validate the ClaimsIdentity in ASP.NET WebAPI

I use the IdentityServer 3 with the ResourceOwner Flow.
It seems to work. I can see in fiddler the token is accessed and validated by my WebAPI.
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "http://server/IdentityServer/",
ValidationMode = ValidationMode.ValidationEndpoint,
RequiredScopes = new[] { "MyFramework" },
SigningCertificate = Certificate.Get(),
DelayLoadMetadata = true
});
If I configure:
ValidationMode = ValidationMode.ValidationEndpoint
There is an extra request.
With ValidationMode.Local there is not. (This is good because IdentityServer is embedded in the WebAPI).
But if I use ValidationMode.Local, there are TWO requests to the IdentityServer.
http://server/IdentityServer/.well-known/openid-configuration
http://server/IdentityServer/.well-known/jwks
Why is this reccessary? The IdentityServer is embedded and this information is accessible in-memory. Even if the setting is "ValidationMode.ValidationEndpoint" this two calls do not appear.
But back to my primary question:
How can I validate the ClaimsIdentity in my WebAPI?
I have an "AuthenticationHandler" (ASP.NET-DelegatingHandler) that handles the validation for my old basic-authentication.
I figured out I can use the HttpContext.Current.User to access the IPrincipal.
Now I check it is of the type "ClaimsIdentity" and the Identity.IsAuthenticated==true.
Is this a correct approach?
And how can I check the User-ID?
On the server-side the ID of the user is put in the "sub"-claim (aka 'Subject').
Do I have to access this specific property to check which user is logged in?
I could access it like this:
ClaimsIdentity.Claims.FirstOrDefault(c => c.Type.Equals("sub"));
BUT only with the setting "ValidationMode.ValidationEndpoint".
Then the AuthenticationType of the IPrincipal is "Bearer" and the 'sub'-claim is present.
With the setting "ValidationMode.Local" the AuthenticationType is "JWT" and the claim is not present.
Why does the ValidationMode changes the IPrincipal? Do I have to check this for different cases?

Login based on user type

I am making a web application using asp.net mvc 3, which has login also.
There are 3 different types of users who will be using the site: Administrator, Operator & Distributor.
How can I create a login that restricts a Distributor from accessing Administrator's & Operator's part of the website. Similarly an Operator should not be able to access Administrator & Distributor part. Also Administrator should not be able to access other type of user's part. i.e. the site should redirect an Administrator type of user to his own part of website after login.
And finally no one should able to access their own part of website without login.
Please could anyone help me.
Look into the membership model of ASP.NET. This model is used to authenticate and authorize users for different parts of your web application.
With the membership model, define three roles within your application: Administrator, Operator and Distributor. Of course, also create users within your application and divide them over these roles.
Last up is the actual authentication and authorization. Use the Authorize attribute to define which role a user must have to access a certain part of your website.
[Authorize(Roles = "Operator")]
public ActionResult OperatorOnlyStuff()
{
return View();
}
And to make sure users should be logged in to even access anything on your site, define a custom authenticated route constraint.
public class AuthenticatedRouteConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
return httpContext.Request.IsAuthenticated;
}
}
And use this custom authenticated route constraint in your default route:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional },
new { isAuthenticated = new AuthenticatedConstraint()}
);

Resources