Can't access client application - c#

This application use Identity 4 with client mvc application and IDP(Identity provider) asp.net core web application.
Can't access Controller Index action view.
How to fix this Issue ????
IDP project Startup project (localhost:44393)
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.GetUsers())
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryClients(Config.GetClients());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseIdentityServer();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
//app.Run(async (context) =>
//{
// await context.Response.WriteAsync("Hello World!");
//});
}
}
Configure.cs file
public static class Config
{
public static List<TestUser> GetUsers()
{
return new List<TestUser>
{
new TestUser
{
SubjectId ="d866oef",
Username ="Kasunjith",
Password="password",
Claims= new List<Claim>
{
new Claim("given_name","Kasunjith"),
new Claim("family_name","Underwood"),
}
}, new TestUser
{
SubjectId ="d866omf",
Username ="BimalJith",
Password="password",
Claims= new List<Claim>
{
new Claim("given_name","BimalJith"),
new Claim("family_name","ViewWord"),
}
},
};
}
// identity-related resources (Scopes)
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>()
{
new Client
{
ClientName="Image Galary",
ClientId="imagegalleryclient",
AllowedGrantTypes = GrantTypes.Hybrid,
RedirectUris = new List<string>()
{
"https://localhost:44335/signin-oidc"
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId
},
ClientSecrets =
{
new Secret("secret".Sha256())
}
}
};
}
}
Client application (localhost:44335)
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
}).AddCookie("Cookies",
(options) =>
{
}).AddOpenIdConnect("oidc", options => {
options.SignInScheme = "Cookies";
options.Authority = "https://localhost:44393";
options.ClientId = "imagegalleryclient";
options.ResponseType = "code id_token";
options.SaveTokens = true;
options.ClientSecret = "secret";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseAuthentication();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Gallery}/{action=Index}/{id?}");
});
}
}
My Controller class
[Authorize]
public class GalleryController : Controller
{
public async Task<IActionResult> Index()
{
await WriteOutIdentityInformation();
return View();
}
public async Task WriteOutIdentityInformation()
{
var identityToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
Debug.WriteLine($"Identity token:{identityToken}");
foreach (var claim in User.Claims)
{
Debug.WriteLine($"Claim type:{ claim.Type} -Claim value : {claim.Value}");
}
}
}
first after login using user name And Password
After go to localhost:44335/Gallary/index show this error

Not 100% sure on this, but I think by default AddOpenIdConnect will request OpenId and Profile scope, however, you've only granted your client OpenId scope, so need to add another one.
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},

Related

Authentication in ASP.Net Core 5

I created asp.net core with React template in VS 2019, i need to authorize a controller method so I first registered my app on Azure AD and than i used this Startup.cs configurations:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddControllersWithViews().AddMicrosoftIdentityUI();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
In the controller I used AuthorizeForScopes and ITokenAcquisition as follows
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ITokenAcquisition tokenAcquisition;
public WeatherForecastController(ITokenAcquisition tokenAcquisition)
{
this.tokenAcquisition = tokenAcquisition;
}
[AuthorizeForScopes(Scopes = new[] { "https://tenantname.sharepoint.com/AllSites.FullControl" })]
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { "https://tenantname.sharepoint.com/AllSites.FullControl" });
......
......
}
}
but when i try to fetch the data i have a CORS error
Can you help me
Regarding the issue, please refer to the following steps
Register client application and server application
Use React project template with ASP.NET Core
Client application
a. Install msal
npm install msal
b. Define MsalAuthProvider.
import React, { Component } from 'react';
import { UserAgentApplication } from 'msal';
const msalConfig = {
authority: 'https://login.microsoftonline.com/common',
clientId: '232a1406-b27b-4667-b8c2-3a865c42b79c',
redirectUri: document.getElementById('root').baseURI
};
export const msalAuth = new UserAgentApplication({
auth: msalConfig
});
export function withAuth(HocComponent) {
return class extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
user: {},
renewIframe: false,
hasError: false,
errorMessage: null
};
}
async componentWillMount() {
msalAuth.handleRedirectCallback(() => {
let userAccount = msalAuth.getAccount();
this.setState({
isAuthenticated: true,
user: userAccount
});
}, (authErr, accountState) => { // on fail
console.log(authErr);
this.setState({
hasError: true,
errorMessage: authErr.errorMessage
});
});
if (msalAuth.isCallback(window.location.hash)) {
this.setState({
auth: {
renewIframe: true
}
});
return;
}
let userAccount = msalAuth.getAccount();
if (!userAccount) {
msalAuth.loginRedirect({});
return;
} else {
this.setState({
isAuthenticated: true,
user: userAccount
});
}
}
onSignIn() {
msalAuth.loginRedirect({});
}
onSignOut() {
msalAuth.logout();
}
render() {
if (this.state.renewIframe) {
return <div>hidden renew iframe - not visible</div>;
}
if (this.state.isAuthenticated) {
return <HocComponent auth={this.state} onSignIn={() => this.onSignIn()} onSignOut={() => this.onSignOut()} {...this.props} />;
}
if (this.state.hasError) {
return <div>{this.state.errorMessage}</div>;
}
return <div>Login in progress...</div>;
}
};
}
c. Update App.js
import { withAuth } from './msal/MsalAuthProvider';
import './custom.css'
class RootApp extends Component {
static displayName ="Azure AD application";
render () {
return (
<Layout>
...
</Layout>
);
}
}
//enable auth when we access the page
const App = withAuth(RootApp)
export default App;
call the API
import { msalAuth } from '../msal/MsalAuthProvider'
async componentDidMount() {
// get token and call the api
try {
const accessTokenRequest = {
scopes: ["api://872ebcec-c24a-4399-835a-201cdaf7d68b/access_as_user"]
}
var authRes
var accessToken = null;
try {
authRes = await msalAuth.acquireTokenSilent(accessTokenRequest);
accessToken=authRes.accessToken
}
catch (error) {
console.log("AquireTokenSilent failure");
authRes = await msalAuth.acquireTokenPopup(accessTokenRequest);
accessToken = authRes.accessToken
}
console.log(accessToken)
this.populateWeatherData(accessToken);
}
catch (err) {
var error = {};
if (typeof (err) === 'string') {
var errParts = err.split('|');
error = errParts.length > 1 ?
{ message: errParts[1], debug: errParts[0] } :
{ message: err };
} else {
error = {
message: err.message,
debug: JSON.stringify(err)
};
}
this.setState({
user: {},
isLoading: false,
error: error
});
}
}
async populateWeatherData(token) {
const response = await fetch('weatherforecast', {
method: 'get',
headers: new Headers({
'Authorization': 'Bearer ' + token
})
});
const data = await response.json();
this.setState({ forecasts: data, loading: false });
}
Server code
a. Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
services.AddControllersWithViews();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
}
b. Controller
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ITokenAcquisition tokenAcquisition;
public WeatherForecastController(ITokenAcquisition tokenAcquisition)
{
this.tokenAcquisition = tokenAcquisition;
}
[AuthorizeForScopes(Scopes = new[] { "<your scope>" })]
[HttpGet]
public async Task<IEnumerable<WeatherForecast>> Get()
{
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { "<your scope>" });
......
......
}
}
For more details, please refer to here snd here.

How to work with SignalR in ASP.NET Core 3.0

We're working on an asp.net core project that depends on SignalR. Lastly we updated our project from ASP.NET Core 2.2 to 3.0 and SignalR stopped working. In the documentation we configured everything (I think correctly) but is still doesn't work. What did we miss?
ASP.NET Core 3.0 API
Startup:
public class Startup
{
private readonly string corsPolicy = "CorsPolicy";
private static string[] AllowdOrigins() => return new string[] {"localhost:4200","example.com"};
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
ConfigureAuthentication(services);
///MICROSOFT SQL DATABASE
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")
));
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.KnownProxies.Add(IPAddress.Parse("XX.XX.XX.XX"));
});
services.AddSignalR();
services.AddControllers();
//services dependencies
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors(corsPolicy);
//app.UseForwardedHeaders(new ForwardedHeadersOptions
//{
// ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
//});
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<ChatHub>("/chat");
});
DummyData.Initialize(app);
}
private void ConfigureAuthentication(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(corsPolicy,
builder =>
{
builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowAnyOrigin()
.AllowCredentials()
.WithOrigins(AllowdOrigins());
});
});
services.AddHttpContextAccessor();
// configure strongly typed settings objects
var appSettingsSection = Configuration.GetSection("AppSettings");
services.Configure<AppSettings>(appSettingsSection);
// configure jwt authentication
var appSettings = appSettingsSection.Get<AppSettings>();
var key = Encoding.ASCII.GetBytes(appSettings.Secret);
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
});
}
}
Chathub:
[EnableCors("CorsPolicy")]
public class ChatHub : Hub
{
private static Dictionary<string, int> onlineClientCounts = new Dictionary<string, int>();
private static readonly string FrontPrefix = "_FRONT";
public ChatHub()
{
}
[HubMethodName("ConnectFrontend")]
public async Task ConnectFrontend(int sessionId)
{
//////
}
[HubMethodName("ConnectDevice")]
public async Task ConnectDevice(int sessionId)
{
//// This method should be called but it isn't.
}
public void DisConnect(int sessionId, int userId)
{
//////////
}
[HubMethodName("SendJsonToFrontends")]
public async Task SendJsonToFrontends(int sessionId, string jsonString)
{
///
}
[HubMethodName("SendJsonToAll")]
public async Task SendJsonToAll(int sessionId, string jsonString)
{
////
}
}
Angular project
SignalRService:
export class SignalRService {
private connection: signalR.HubConnection;
public newMessage = new Subject<SocketMessage>();
constructor() {
}
public connectFront() {
this.connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:2525/chat")//(environment.baseSignalR)
.configureLogging(signalR.LogLevel.Trace)
.build();
this.connection.on("ReceiveJson", data => { this.handleJsonMessage(data) });
// handles the first connection message
this.connection.start().then(() => this.sendConnectionMessage());
// handles the incoming messages
this.connection.on("ReceiveJson", data => this.handleJsonMessage(data));
this.connection.on("ReceiveJsonFrontend", data => this.handleJsonMessage(data));
}
private sendConnectionMessage() {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("ConnectFrontend", sessionId).catch((error) => { debugger; console.log(error); });
}
}
public sendWebsocketMessageToAll(msg: SocketMessage) {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("SendJsonToAll", sessionId, JSON.stringify(msg)).catch((error) => console.log(error));
}
}
public sendWebsocketMessageToFrontend(msg: SocketMessage) {
var sessionId = sessionStorage.getItem("SessionId");
if (sessionId != null) {
this.connection.invoke("SendJsonToFrontends", sessionId, JSON.stringify(msg)).catch((error) => console.log(error));
}
}
private handleJsonMessage(data: string) {
this.newMessage.next(this.getSocketMessage(data));
}
public getSocketMessage(data: string): SocketMessage {
var msg: SocketMessage = JSON.parse(data);
return msg;
}
public disconnect() {
this.connection.stop();
}
}
angular output:
Api output:
Just create an web app API with angular template you can view my code for your ref
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddSignalR().AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.WriteIndented = false;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
if (!env.IsDevelopment())
{
app.UseSpaStaticFiles();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapHub<ChatHub>("/chatHub");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
}
In FE side. Note: use new package #microsoft/signalr
import * as signalR from "#microsoft/signalr";
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
title = 'app';
ngOnInit() {
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chathub")
.build();
connection.on("receiveMessage", (username: string, message: string) => {
console.log(username);
console.log(message);
});
connection.start().then(() => {
connection.send("sendMessage", "aaa", "aaaa")
.then(() => console.log("done"));
}).catch(err => document.write(err));
}
}
Well I finaly figured it out with the help of Tony,
Apperently It went wrong in the methods of the SignalR chat hub. The methods allowed only integers as parameters but it needed to be strings. I don't know it it has to do with some other settings but my guess now is that signalr cant convert the request parameters to something other then strings.
When I changed it to strings it worked.
It seems, that you have configured JWT based authentication on the server side and do not provide a token for SignalR connection. Try to provide a token using the accessTokenFactory:
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(`${environment.urlAddress}/chathub`, {
accessTokenFactory: () => this.token
})
.build()

Asp.net Core 2.2, custom middleware for authorizing files outside of wwwroot, but httpcontext.User is null

I need to allow access to a directory of static files only if the user is authenticated.
I have set up a middleware to check each request as it comes. I can get the request no problem, but the Httpcontext User is always null. I am using cookies authentication and not Identity. I have added the default authentication cookies scheme to the sign in as well as the services in startup.cs. I'm not sure why I can't get the user. This is based off of Scott Allen's tutorial https://odetocode.com/blogs/scott/archive/2015/10/06/authorization-policies-and-middleware-in-asp-net-5.aspx
Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication( options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.AccessDeniedPath = "/Home/Index";
options.LoginPath = "/Identity/Account/Login";
});
services.AddAuthorization(options =>
{
options.AddPolicy("Authenticated", policy => policy.RequireAuthenticatedUser());
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseProtectFolder(new ProtectFolderOptions
{
Path = "/StaticFiles",
PolicyName = "Authenticated",
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), "Static_Files")),
RequestPath = "/StaticFiles"
});
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
private object RedirectResult()
{
throw new NotImplementedException();
}
}
MiddleWare
public class ProtectFolderOptions
{
public PathString Path { get; set; }
public string PolicyName { get; set; }
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class ProtectFolderExtensions
{
public static IApplicationBuilder UseProtectFolder(this IApplicationBuilder builder, ProtectFolderOptions options)
{
return builder.UseMiddleware<ProtectFolder>(options);
}
}
// You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
public class ProtectFolder
{
private readonly RequestDelegate _next;
private readonly PathString _path;
private readonly string _policyName;
public ProtectFolder(RequestDelegate next,ProtectFolderOptions options)
{
_next = next;
_path = options.Path;
_policyName = options.PolicyName;
}
public async Task Invoke(HttpContext httpContext, IAuthorizationService authorizationService)
{
if (httpContext.Request.Path.StartsWithSegments(_path))
{
var authorized = await authorizationService.AuthorizeAsync(
httpContext.User, null, _policyName);
if (authorized.Succeeded == false)
{
await httpContext.ChallengeAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
}
await _next(httpContext);
}
}
The middleware is able to check the request. But httpcontext doesn't contain user even after I signed in, and I always get redirected to the login page.

Secure asp.net core web api using authorize attributes + identity Server 4

I managed to set up an Identity Server 4 using is4aspid from their templates and it seems to be working fine. Now I'm trying to protect a Web Api using Asp.net Core 2.0. The Authorize seems to be working, but when I try to use [Authorize(Roles ="Admin")] in my controller method it won't work.
I watched this video and tried to do what they did, but I can't find a good chunk of the code they use, like AuthorizationProviderClient or app.UseAuthorizationProvider()
This is the Startup.cs ConfigureServices method of my Identity Server:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer (Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddMvc();
services.Configure<IISOptions>(iis =>
{
iis.AuthenticationDisplayName = "Windows";
iis.AutomaticAuthentication = false;
});
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiResources(Config.GetApis())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>();
if (Environment.IsDevelopment())
{
builder.AddDeveloperSigningCredential();
}
else
{
throw new Exception("need to configure key material");
}
services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId = "xxxx-xxxx.apps.googleusercontent.com";
options.ClientSecret = "xxxxx";
});
}
And this is the config class:
public static class Config
{
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
}
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
//new ApiResource("api1", "My API #1")
new ApiResource("api1", "My API #1") { UserClaims = { "role" } }
};
}
public static IEnumerable<Client> GetClients()
{
return new[]
{
// https://www.scottbrady91.com/Angular/SPA-Authentiction-using-OpenID-Connect-Angular-CLI-and-oidc-client
new Client {
ClientId = "angular_spa",
ClientName = "Intranet Web App",
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = new List<string> { "openid", "profile", "api1" },
RedirectUris = new List<string> { "http://localhost:4200/auth-callback", "http://localhost:4200/silent-refresh.html" },
PostLogoutRedirectUris = new List<string> { "http://localhost:4200/" },
AllowedCorsOrigins = new List<string> { "http://localhost:4200" },
AllowAccessTokensViaBrowser = true
}
};
}
}
And this is the Startup.cs ConfigureServices of my WebApi:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
options.EnableCaching = false;
options.RoleClaimType = System.Security.Claims.ClaimTypes.Role;
});
}
And then the configure:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
This is how the roles are in the database:
I got this API from their samples. When I send the Bearer token using Postman the Authorize attribute works fine, now I want to get the Roles to work as well. Am I missing a nuget package or something?
Thanks in advance for any help

.net Core Integration Test : API Controller Action Not getting called from Test. How to mock opeind connect authentication in test project?

start up cs file .net core: (This is also get called while creating test server)
public class Startup
{
private IHostingEnvironment env;
private Dictionary<string, string> secretlist = new Dictionary<string, string>();
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
this.Configuration = configuration;
this.CurrentEnvironment = env;
}
public Startup(IHostingEnvironment env)
{
this.env = env;
}
public IConfiguration Configuration { get; }
private IHostingEnvironment CurrentEnvironment { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<FormOptions>(x =>
{
x.ValueLengthLimit = int.MaxValue;
x.MultipartBodyLengthLimit = int.MaxValue;
x.MultipartHeadersLengthLimit = int.MaxValue;
});
services.AddApplicationInsightsTelemetry(this.Configuration);
services.AddSingleton<ITelemetryInitializer, AppInsightsInitializer>();
// Adds services required for using options.
services.AddOptions();
services.Configure<AppSettingsConfig>(this.Configuration.GetSection("AppSettings"));
if (this.CurrentEnvironment.IsDevelopment())
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Environment.ExpandEnvironmentVariables(this.Configuration.GetValue<string>("AppSettings:KeyStorage_UNCPath"))))
.ProtectKeysWithDpapiNG();
}
else
{
CloudStorageAccount storageAccount = new CloudStorageAccount(
new Microsoft.WindowsAzure.Storage.Auth.StorageCredentials(
this.Configuration.GetValue<string>("AppSettings:StorageAccountName"),
this.Configuration.GetValue<string>("AppSettings:StorageAccessValue")), true);
//Create blob client
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
// Get a reference to a container named "keycontainer."
CloudBlobContainer container = blobClient.GetContainerReference("keycontainer");
services.AddDataProtection().PersistKeysToAzureBlobStorage(container, "keys.xml");
}
services.Configure<AppSettingsConfig>(options =>
{
if (!this.CurrentEnvironment.IsDevelopment())
{
}
});
var azureAdConfig = new AzureAdConfig();
this.Configuration.GetSection("Authentication:AzureAd").Bind(azureAdConfig);
services.Configure<AzureAdConfig>(this.Configuration.GetSection("Authentication:AzureAd"));
var connectionStringsConfig = new ConnectionStringsConfig();
connectionStringsConfig.oneConnection = this.secretlist["ConnectionStrings"];
//this.Configuration.GetSection("ConnectionStrings").Bind(connectionStringsConfig);
//services.Configure<ConnectionStringsConfig>(this.Configuration.GetSection("ConnectionStrings"));
if (this.RequireAAD())
{
// Add framework services.
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
}
else
{
services.Configure<MvcOptions>(options =>
{
});
}
// Add Authentication services.
if (this.RequireAAD())
{
// Configure the OWIN pipeline to use cookie auth.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
// Configure the OWIN pipeline to use OpenID Connect auth.
// https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-openid-connect-code
.AddOpenIdConnect(options =>
{
options.ClientId = azureAdConfig.ClientId;
options.ClientSecret = azureAdConfig.ClientSecret;
options.Authority = string.Format(azureAdConfig.AADInstance, azureAdConfig.Tenant);
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.Resource = azureAdConfig.ResourceURI_Graph;
// PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
options.Events = new AuthEvents(azureAdConfig, connectionStringsConfig);
});
if (this.RequireAAD())
{
services.AddMvc(config =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new Microsoft​.AspNetCore​.Mvc​.Authorization.AuthorizeFilter(policy));
config.Filters.Add(typeof(ExceptionFilter));
});
}
else
{
services.AddMvc();
}
if (this.Configuration.GetValue<bool>("API: SWITCH_ENABLE_API", true))
{
//services.AddScoped<IDBOperation, Operations>();
services.AddScoped<ILookupSearch, Operations>();
services.AddScoped<IFullSearch, Operations>();
}
services.AddSingleton<Common.Data.RepositoryFactories>(new Common.Data.RepositoryFactories(new Dictionary<Type, Func<DbContext, object>>
{
{ typeof(IQueryRepository), dbcontext => new QueryRepository(dbcontext) },
{ typeof(IDomainValuesRepository), dbcontext => new DomainValuesRepository(dbcontext) },
{ typeof(IRequestsRepository), dbcontext => new RequestsRepository(dbcontext) },
// { typeof(IoneDomainValuesRepository), dbcontext => new oneDomainValuesRepository(dbcontext) }
}));
services.AddTransient<Common.Contracts.IRepositoryProvider, Common.Data.RepositoryProvider>();
services.AddScoped<one.Data.Contracts.IoneUow, one.Data.oneUow>();
services.AddTransient<IUow,Uow>();
// For accessing appinsights for dependency injection?
services.AddApplicationInsightsTelemetry();
// For Storing Tokens in DB
services.AddDistributedSqlServerCache(o =>
{
o.ConnectionString = this.secretlist["ConnectionStrings"];
// o.ConnectionString = this.Configuration.GetConnectionString("oneConnection");
// o.ConnectionString = this.Configuration[this.Configuration.GetSection("KeyVaultSeetings")["oneConnectionString"]];
o.SchemaName = "dbo";
o.TableName = "CacheTable";
});
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IEntityExtractor, EntityExtractor>();
services.AddScoped<ITokenCacheService, DistributedTokenCacheService>();
services.AddScoped<ITokenService, TokenService>();
services.AddTransient<IAPIClient,APIClient>();
}
/// <summary>
/// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
/// </summary>
/// <param name="app"></param>
/// <param name="env"></param>
/// <param name="loggerFactory"></param>
/// <param name="tc"></param>
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, TelemetryClient tc)
{
var azureAdConfig = new AzureAdConfig();
this.Configuration.GetSection("Authentication:AzureAd").Bind(azureAdConfig);
loggerFactory.AddConsole(this.Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddProvider(new MyFilteredLoggerProvider(tc));
loggerFactory.AddApplicationInsights(app.ApplicationServices, this.Configuration.GetValue<string>("Logging:LogLevel:Default") == "Information" ? Microsoft.Extensions.Logging.LogLevel.Information : Microsoft.Extensions.Logging.LogLevel.Warning);
this.SetupStore(app);
app.UseRewriter(new RewriteOptions().AddRedirectToHttps());
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
HotModuleReplacement = true
});
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// TODO . Switch
app.UseStaticFiles();
if (this.RequireAAD())
{
app.UseAuthentication();
}
app.UseMiddleware(typeof(ErrorHandlingMiddleware));
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
Controller is decorated as :
[Route("api/[controller]")]
public class SearchController : BaseController
Controller Action is decorated as :
[Route("TestMethod")]
[ActionName("TestMethod")]
[HttpGet]
public async Task<EmptyResult> Test()
Configuration of TestServer Test CS file :
public DemoTest()
{
// Set up server configuration
var configuration = new ConfigurationBuilder()
.AddJsonFile(#"appsettings.json")
.Build();
// Create builder
var builder = new WebHostBuilder()
.UseStartup<Startup>()
.UseConfiguration(configuration);
// Create test server
var server = new TestServer(builder);
// Create database context
this._context = server.Host.Services.GetService(typeof(DBContext)) as DBContext;
// Create client to query server endpoints
this._client = server.CreateClient();
_client.BaseAddress = new Uri("https://localhost:44316/");
}
Test as a Fact :
[Fact]
public async Task Test()
{
try
{
var response = await this._client.GetAsync("/api/Search/TestMethod");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
//Assert.False(result != null);
}
catch (Exception ex)
{
throw;
}
}
Getting Status as 302 and SearchController action is not getting
called. All the dependencies are resolved using start up configuration
file
Any idea ???
You could check the content for var responseString = await response.Content.ReadAsStringAsync(); to see what the content is.
I assume it is the login page which is due to that you required Authorize.
First, try to remove the code below for a try.
services.AddMvc(config =>
{
//var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
//config.Filters.Add(new Microsoft​.AspNetCore​.Mvc​.Authorization.AuthorizeFilter(policy));
config.Filters.Add(typeof(ExceptionFilter));
});
For Authentication, you will need to imitate the login process, here is a link for Identity, you could try implement your own login for AAD.
Razor Pages .NET Core 2.1 Integration Testing post authentication

Categories