ASP.NET Authorize Filter Denies Access for User in Specified Role - asp.net-core

In my ASP.NET Core 2.0 Application, I am stuck with an issue an Admin logged in User cannot access controller I used the Authorize Filter on [Authorize(Policy="AdminAlone")].
I confirmed that the user is in the "Administrators" role and added the policy in startup.cs but it redirects to an AccessDenied view when I try to access the controller.
I saw a similar problem on this link, but the solution didn't help me
Startup Class in MVC Client - ConfigureServices
services.AddMvc();
services.AddSession();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthorization(options =>
{
options.AddPolicy("AdminAlone", policy => policy.RequireRole("Administrators"));
});
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultForbidScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie("Cookies")
.AddOpenIdConnect("Bearer", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ClientId = "mvcWeb";
options.ClientSecret = "spring12345";
options.ResponseType = OidcConstants.ResponseTypes.CodeIdToken;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("NuB.HospitalSearch");
options.Scope.Add("offline_access");
});
Web API ConfigureServices
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler
{
InboundClaimTypeMap = new Dictionary<string, string>()
};
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(option =>
{
option.Audience = "NuB.HospitalSearch";
option.Authority = "http://localhost:5000";
option.RequireHttpsMetadata = false;
option.SecurityTokenValidators.Clear();
option.SecurityTokenValidators.Add(jwtSecurityTokenHandler);
option.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateAudience = true,
ValidAudience = "NuB.HospitalSearch",
ValidateIssuer = true
};
});

You may try the following. Inside your AddOpenIdConnect configuration add
options.TokenValidationParameters = new TokenValidationParameters {
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
In fact, this property defines the types and definitions required for validating a token. Please refer to this post from Dominick Baier for a more detailed explanation.

Related

TokenValidationParameters validation for multi-tenant ASP.NET Core Web API application

This code is working for a single tenant application. How does it need to be changed to work with multi-tenant application (Web API)? Is setting ValidateIssuer = false the right way?
sample
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
var azureAdOptions = new AzureADOptions();
Configuration.Bind("AzureAd", azureAdOptions);
options.Authority = $"{azureAdOptions.Instance}{azureAdOptions.TenantId}/v2.0";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidAudiences = SSOAuthHelper.GetValidAudiences(Configuration),
ValidIssuers = SSOAuthHelper.GetValidIssuers(Configuration),
AudienceValidator = SSOAuthHelper.AudienceValidator
};
});

SignalR authentication with Blazor wasm Bearer Token

I authenticated to Signalr with Bearer token and signalr said Authentication was
successful.
But Signalr Hub Context.Identity.User.Name is null after authentication.
How can i access to authenticated user information in SignalR Hub for connectionid and user mapping.
My Startup.cs code for authentication.
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "https://security.minima.local";
options.Audience = "blazor_web_app_api";
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new
TokenValidationParameters()
{
ValidateAudience = false
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/notification")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
})
.AddIdentityServerJwt();
You need to tell the TokenValidator which claim is the name and role claim and you do that by setting:
.AddMyJwtBearer(opt =>
{
...
opt.TokenValidationParameters.RoleClaimType = "roles";
opt.TokenValidationParameters.NameClaimType = "name";

IdentityServer 4 WsFederation - How to get access token for calling API

I am using Identity Server 4 with the Ws-Federation plugin. Identity Server is configured to connect to Azure AD for authentication. Here is the relevant code from Identity Server project:
public void ConfigureServices(IServiceCollection services)
{
var rsaCertificate = new X509Certificate2("rsaCert.pfx", "1234");
services.AddRazorPages();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<User, IdentityRole>(options =>
{
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(2);
options.Lockout.MaxFailedAccessAttempts = 3;
})
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserStore<CustomUserStore>()
.AddUserManager<CustomUserManager>()
.AddDefaultTokenProviders();
services.AddTransient<IUserStore<User>, CustomUserStore>();
services.AddTransient<IEmailSender, EmailSender>();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddSigningCredential(rsaCertificate)
.AddInMemoryIdentityResources(IdentityConfig.IdentityResources)
.AddInMemoryApiScopes(IdentityConfig.ApiScopes)
.AddInMemoryClients(IdentityConfig.Clients)
.AddAspNetIdentity<User>()
.AddWsFederationPlugin(options =>
{
options.Licensee = "Licensee";
options.LicenseKey = "LicenseKey";
})
.AddInMemoryRelyingParties(new List<RelyingParty>());
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddWsFederation(options =>
{
options.Wtrealm = "Azure AD App Id";
options.MetadataAddress = "WSFed metadata URL from Azure AD App";
options.Events.OnSecurityTokenValidated = SecurityTokenValidated;
})
.AddCookie(options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(IdentityConfig.SessionTimeoutInMinutes);
options.SlidingExpiration = true;
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
});
}
I have an API protected with JWT bearer authentication which is connected to the same Identity Server. The relevant code from the API (Please note that https://localhost:5001 is the address which the Identity Server is running in):
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://localhost:5001";
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
}
I have an MVC client as well which is connected to the same Identity Server. I was able to successfully authenticate users from the MVC client. Now, what I would like to do is to call a protected API endpoint in the API project from within the MVC client. I haven't found any way to get the access token necessary for calling the protected API. Relevant code from the MVC client:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = WsFederationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Name = "aspnetcorewsfed";
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(Configuration.GetValue<int?>("SessionTimeoutInMinutes") ?? 15);
})
.AddWsFederation(options =>
{
options.MetadataAddress = "https://localhost:5001/wsfed"; // Address of the Identity Server
options.RequireHttpsMetadata = false;
options.Wtrealm = "mvc"; // ClientId registered in Identity Server
options.CallbackPath = "/";
options.SkipUnrecognizedRequests = true;
});
}
There's documentation on the Identity Server website that describes how to access protected APIs as seen here. But this is using OpenIdConnect. Since I am using WsFederation, I have no clue on how to get the access token or refresh token. Is token refresh impossible with WsFed?
Can anyone point me in the right direction on how to go about this?
Use OpenIdConnect in the MVC client instead of WsFed. Change the code in the MVC client's Startup.cs to the following:
services.AddAuthentication(options =>{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies").AddOpenIdConnect("oidc", options =>{
options.Authority = "https://localhost:5001";
options.ClientId = "mvc-openid";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
});
The corresponding client registration in Identity Server should be:
new Client {
ClientId = "mvc-openid",
ClientSecrets = {
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = {
"https://localhost:6001/signin-oidc"
},
AllowedScopes = new List < string > {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"api"
}
}
https://localhost:5001 is the Identity Server address and https://localhost:6001 is the MVC client address.
The access token for accessing the API can be obtained like so:
var accessToken = await HttpContext.GetTokenAsync("access_token");

Auth0 + Swashbuckle .Net Core 2.2. Missing claims in jwt token when using SwaggerUI

I am making a ASP.Net Core WebApi which is authentication via Auth0. I am using Swagger and SwaggerUI and trying to authenticate from Swagger UI.
// Add authentication services
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options =>
{
// Set the authority to your Auth0 domain
options.Authority = $"https://{Configuration["Auth0:Authority"]}";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
// Set response type to code
options.ResponseType = "code";
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.SaveTokens = true;
// Set the callback path, so Auth0 will call back to http://localhost:3000/callback
// Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard
options.CallbackPath = new PathString("/callback");
// Configure the Claims Issuer to be Auth0
options.ClaimsIssuer = "Auth0";
// Saves tokens to the AuthenticationProperties
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("audience", #"https://predictor-dev.api");
return Task.FromResult(0);
},
// handle the logout redirection
OnRedirectToIdentityProviderForSignOut = (context) =>
{
var logoutUri = $"https://{Configuration["Auth0:Authority"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}";
var postLogoutUri = context.Properties.RedirectUri;
if (!string.IsNullOrEmpty(postLogoutUri))
{
if (postLogoutUri.StartsWith("/"))
{
// transform to absolute
var request = context.Request;
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
}
logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}";
}
context.Response.Redirect(logoutUri);
context.HandleResponse();
return Task.CompletedTask;
}
};
})
.AddJwtBearer(options =>
{
options.Authority = Configuration["Auth0:Authority"];
options.Audience = Configuration["Auth0:Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/roles"
};
options.ClaimsIssuer = "Auth0";
});
services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder =>
{
builder
.WithOrigins(Configuration["FrontendBaseUrl"])
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "Predictor API", Version = "v1" });
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"{Configuration["Auth0:Authority"]}authorize?audience={Configuration["Auth0:Audience"]}",
Scopes = new Dictionary<string, string>
{
{ "read:books", "Access read book operations" },
{ "write:books", "Access write book operations" }
}
});
c.OperationFilter<SecurityRequirementsOperationFilter>();
});
Here is the token which is returned after authentication via SwaggerUI:
{
"iss": "my iss",
"sub": "my sub",
"aud": "my aud",
"iat": 1556002815,
"exp": 1556010015,
"azp": "azp",
"scope": "read:books"
}
The problem here is that token doesn't have openid and profile information.
I don't have any custom rules in Auth0 that could limit my scopes (I removed them totally).I tried different options, but I could not get any additional claims.
Is there any configuration in Swagger that I am missing?
Thank you.
You have to pass "openid" and "profile" scopes to extend your token with openid and profile information

IdentityServer4: get access token from Azure AD

I am using Azure AD as an external IdP with IdentityServer4. To call an API that is protected with AzureAd, I need to get access token from Azure Ad. Is it possible to get the access token as part of the login process and save it to claims?
I am using IdentityServer4 Quickstart UI. I tried to capture access token in the callback method of external token, but did not find that in the HttpContext or the claims or in the ProcessLoginCallbackForOidc method.
IdentityServer4 Azure Ad Configuration:
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
services.AddAuthentication()
.AddOpenIdConnect("oidc", "Azure AD", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "https://login.microsoftonline.com/fredhutch.onmicrosoft.com/";
options.ClientId = "<client id>";
options.Resource = "app_id from azure ad";
options.ClientSecret = "secret from azure ad";
options.ResponseType = "code id_token";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "sub",
RoleClaimType = "role"
};
});
Client configuration in IdentityServer4:
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
RedirectUris = { "http://localhost:49341/signin-oidc" },
PostLogoutRedirectUris = { "http://localhost:49341/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"b03d4318-278d-40fc-b6b3-3cf47a0e6f4d"
},
AllowOfflineAccess=true
}
Client (ASP.Net Core MVC):
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.SignInScheme = "Cookies";
options.Authority = "idsrv4url";
options.ClientId = "mvc";
options.ClientSecret = "secret";
options.SaveTokens = true;
options.ResponseType = "code id_token";
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("b03d4318-278d-40fc-b6b3-3cf47a0e6f4d");
options.Scope.Add("offline_access");
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
});
Your setup against Azure AD is an implicit flow, meaning you only get an authorization code and id token (based on your responsetype = "code id_token").
What you need to do is subscribe to the OnAuthorizationCodeReceived event and ask for access token here.
options.Events.OnAuthorizationCodeReceived= contex => {
var authCode = contex.ProtocolMessage.Code;
...
// Get token
...
};
You can find more info here https://learn.microsoft.com/en-us/azure/active-directory/develop/v1-protocols-oauth-code#use-the-authorization-code-to-request-an-access-token
I was able to get the Azure AD id_token to show up in the ExternalController.Callback() (and therefore ProcessLoginCallbackForOidc()) in the QuickStart UI template by adding the SaveTokens flag into the IdentityServer OIDC setup:
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
services.AddAuthentication()
.AddOpenIdConnect("oidc", "Azure AD", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.Authority = "https://login.microsoftonline.com/fredhutch.onmicrosoft.com/";
options.ClientId = "<client id>";
options.Resource = "app_id from azure ad";
options.ClientSecret = "secret from azure ad";
options.ResponseType = "code id_token";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "sub",
RoleClaimType = "role"
};
options.SaveTokens = true;
});
With that flag set, the following code will now retrieve the AAD id_token successfully:
//External OpenId Connect callback
public async Task<IActionResult> Callback()
{
var result = await HttpContext.AuthenticateAsync(IdentityConstants.ExternalScheme);
var id_token = result.Properties.GetTokenValue("id_token");
...
}