OrchardCore Multi Tennant with Web Api - asp.net-core-webapi

Is it possible to use the multi tennant features of OrchardCore in a Web API project?
I have created an empty project in Visual Studio using the using the "ASP.NET Core Web Application" -> "API" template and added a reference to OrchardCore.Application.Mvc.Targets.
My StartUp class looks like this:
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.AddControllers();
services.AddOrchardCore().AddMvc().WithTenants();
}
// 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();
}
app.UseOrchardCore(builder =>
{
builder.UseHttpsRedirection();
builder.UseRouting();
builder.UseAuthorization();
builder.UseEndpoints(endpoints => { endpoints.MapControllers(); });
});
}
}
I have also added the following to appsetting.json:
"OrchardCore": {
"Default": {
"State": "Running",
"RequestUrlHost": null,
"RequestUrlPrefix": null,
"Features": [],
"CustomTitle": "Default Tenant",
"CustomSetting": "Custom setting for Default tenant"
},
"CustomerA": {
"State": "Running",
"RequestUrlHost": null,
"RequestUrlPrefix": "customer-a",
"Features": [ "Module1" ],
"CustomTitle": "Customer A",
"CustomSetting": "Custom setting for Customer A"
},
"CustomerB": {
"State": "Running",
"RequestUrlHost": null,
"RequestUrlPrefix": "customer-b",
"Features": [ "Module1", "Module2" ],
"CustomTitle": "Customer B",
"CustomSetting": "Custom setting for Customer B"
}
When I run the application the default https://localhost:44370/weatherforecast route works but https://localhost:44370/customer-b/weatherforecast returns a 404.
I think it may be the way I am using AddControllers or UseOrchardCore. For example if I comment out AddControllers I get an InvalidOperationException becuase UseAuthorization no longer works.

Using Core 3.1
I did this in my StartUp to let me use controllers instead of pages.
Keep in mind that you have to add OrchardCore.Module.Targets to each module and then import the module to the main project.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new TenantViewLocationExpander());
});
/*
* Needed for authorization and such.
*/
services.AddControllersWithViews();
/*
* Add multitenancy
*/
services.AddOrchardCore()
.Configure((tenantAppBuilder, endpointBuilder, tenantScopeServiceProvier) =>
{
/*
* Map our routes
*/
endpointBuilder.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
}).AddMvc()
.WithTenants();
}

Related

Authorize for Azure AD groups in asp.net mvc

I am trying to use authorization on specific page views in controllers with [Authorize(Policy = "nameOfPolicy")] but I keep getting "Access denied" even though I have access to the Azure AD group that I have entered in my policy.
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)
{
// Get the scopes from the configuration (appsettings.json)
var initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
// Add sign-in with Microsoft
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
// Add the possibility of acquiring a token to call a protected web API
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
// Enables controllers and pages to get GraphServiceClient by dependency injection
// And use an in memory token cache
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddDistributedTokenCaches();
services.AddAuthorization(options =>
{
options.AddPolicy("it", policy => policy.RequireClaim("groups", "Azure group ID here"));
});
// Register AadService and PbiEmbedService for dependency injection
services.AddScoped(typeof(AadService))
.AddScoped(typeof(PbiEmbedService))
.AddScoped(typeof(PowerBiServiceApi));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
// Enables a UI and controller for sign in and sign out.
services.AddRazorPages()
.AddMicrosoftIdentityUI();
// Session/cookie variables etc
services.AddDistributedMemoryCache();
services.AddSession();
// Loading appsettings.json in C# Model classes
services.Configure<AzureAd>(Configuration.GetSection("AzureAd"))
.Configure<PowerBI>(Configuration.GetSection("PowerBI"));
// Add the UI support to handle claims challenges
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
}
// 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("/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.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
}
And in my controller this is how I try to use the Authorize:
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
[Authorize(Policy = "it")]
public Task<IActionResult> Index()
To authorize for Azure AD groups in asp.net MVC.
I have followed the below steps and able to authorize.
Create an APP in Azure AD and register the App.
Using the ID tokens in authentication for the App.
Set the RedirectionUrl in azure for the App from the authentication tab.
Choose the ASP.Net MVC application from Visual studio templates and install the below NuGet packages.
NuGets
Microsoft.AspNetCore.Authentication.AzureAD.UI
Microsoft.Identity.Web
In Startup.cs class make the below changes to register or configure the services of Authentication
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApp(Configuartion.GetSetion("AzureAd"));
services.AddControllersWithViews();
}
And you need add app.UseAuthentication () method along with app.UseAuthorization() in startup.cs class.
And you need to use the TenantId , ClientId and RedirectionUrl in the Settings.Json file.
appsettings.json
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "Project",
"ClientId": "",
"TenantId": "",
"CallbackPath": "/signin-oidc"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
launchSettings.Json
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:42313",
"sslPort": 44302
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"MVC_APP": {
"commandName": "Project",
"launchBrowser": true,
"applicationUrl": "https://localhost:41222",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Authorize attribute need to be add at the controller level.
[Authorize]
public class HomeController : Controller

ASP.Net Core WebApi projects in VS 2022: 404 when renaming controller

I rarely dive into web development and was using the following link to build a small project: https://learn.microsoft.com/en-us/visualstudio/javascript/tutorial-asp-net-core-with-angular?view=vs-2022
Using this project I've gone to rename the controller and now when loading the page the request returns a 404. If I name it back the page loads as expected (in the three components below by renaming Products to WeatherForecast). The 3 places I re-named are below. This is still using dummy data as I learn angular.
What am I missing by doing a simple rename here?
product.component.ts:
export class ProductComponent implements OnInit {
public forecasts?: Products[];
constructor(http: HttpClient) {
http.get<Products[]>('/products').subscribe(result => {
this.forecasts = result;
}, error => console.error(error));
}
proxy.conf.js:
const PROXY_CONFIG = [
{
context: [
"/products",
],
target: "https://localhost:7050",
secure: false
}
]
module.exports = PROXY_CONFIG;
Products controller:
using Microsoft.AspNetCore.Mvc;
namespace WebApplication1.Controllers
{
[ApiController]
[Route("[controller]")]
public class ProductsController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger<ProductsController> _logger;
public ProductsController(ILogger<ProductsController> logger)
{
_logger = logger;
}
[HttpGet(Name = "GetProducts")]
public IEnumerable<Products> Get()
{
return Enumerable.Range(1, 5).Select(index => new Products
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
404:
According to your HttpErrorResponse, you are not sending request to .NET API, but to localhost:4200 which I believe is your Angular application.
Try sending request like this:
http.get<Products[]>('https://localhost:7050/products').subscribe...
If that works, your proxy configuration is messing with the request. Be sure to get the port right and to start Angular application after .NET application has started. You can check your ports in launchSettings.json, be careful because http and https use different ports. Use http if your certificates are not configured.
Don't forget to include proxy.conf.json in angular.json under "serve" like this:
"architect": {
"serve": {
"builder": "#angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "your-application-name:build",
"proxyConfig": "src/proxy.conf.json"
},
To extend #Ocram's answer - which is absolutely correct and relevant -, I also found that if you have a context: [] list in your PROXY_CONFIG definition in the proxy.conf.js file (the list of paths/controllers you want to redirect to the backend port), then you need to ensure that this list contains all the controller names you need.
It is because when you rename a controller, you also need to inform the client proxy config about the change.
const PROXY_CONFIG = [
{
///-----------------
context: [
"/weatherforecast",
"/test",
"/mynewapi",
],
///-----------------
changeOrigin: true,
target: target,
secure: false,
headers: {
Connection: 'Keep-Alive'
}
}
]

How can I pass the connectionstring from my selected publish-profile into the configuration of my Startup.cs-File?

We have two different connectionstrings in the file appsettings.json. DevConnection is the database for our test system. ProdConnection is the database for our productive system:
{
"ConnectionStrings": {
"DevConnection": "server=TestServer;Database=MyDatabase;user id=sql-User;Password=***;",
"ProdConnection": "server=ProdServer;Database=MyDatabase;user id=sql-User;Password=***;"
},
[...]
}
We have also two publishing profiles for test-server and productive-server. One for the testsystem. One for the productive system. We want to apply the connectionstrings DevConnection/ProdConnection for the publishing profiles test-server and productive-server:
How can I pass the connectionstring from my selected publish-profile into the configuration of my Startup.cs-File?
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddControllers().AddJsonOptions(opts => opts.JsonSerializerOptions.PropertyNamingPolicy = null);
services.AddDbContext<KundenDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("?")));
}
Here is the structure of appsettings.json:
You can add different connction according to different environments.
You can add this to appsettings.Production.json:
"ConnectionStrings": {
"Connection": "server=ProdServer;Database=MyDatabase;user id=sql-User;Password=***;"
},
add this to appsettings.Development.json:
"ConnectionStrings": {
"Connection": "server=TestServer;Database=MyDatabase;user id=sql-User;Password=***;",
},
startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddRazorPages();
services.AddControllers().AddJsonOptions(opts => opts.JsonSerializerOptions.PropertyNamingPolicy = null);
services.AddDbContext<KundenDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Connection")));
}

Azure AD B2C Web API Using .NET Core 2

I’m trying to call a Web API from a Web App both protected by Azure AD B2C. The App signs in fine with the Azure sign in page. But when I call my [Authorize] endpoint on my API I get a 401 unauthorized response.
I thought this is supposed to work right out of the box using VS2017 and ASP.NET Core 2.1. When I created both apps I specified “Individual User Accounts” for authentication and “Connect to an existing user store in the cloud”. The examples I’ve found seem to be from .NET Core 1 or older and no longer relevant or using deprecated setups.
I have the App in the API access section with read and write scopes in Azure.
How do I successfully authorize my App to call my API?
Here’s my App Startup.cs:
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.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
.AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// 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();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
My App appsettings.json:
{
"AzureAdB2C": {
"Instance": "https://myCompanyPassport.b2clogin.com/tfp/",
"ClientId": "51dde0de-a204-4b67-b890-068846e17ff1",
"ClientSecret": "------------------------",
"CallbackPath": "/signin-oidc",
"Domain": "myCompanyPassport.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_myCompanySignUpSignIn",
"ResetPasswordPolicyId": "B2C_1_myCompanyPasswordReset",
"EditProfilePolicyId": "B2C_1_myCompanyProfile",
"TaskServiceUrl": "https://localhost:44337/",
"ApiIdentifier": "https://myCompanyPassport.onmicrosoft.com/taskapi",
"ReadScope": "read",
"WriteScope": "write"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Here’s my API Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// 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();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
My API appsettings.json:
{
"AzureAdB2C": {
"Instance": "https://myCompanyPassport.b2clogin.com/tfp/",
"ClientId": "213764b3-8c2a-4bf6-9e69-355495a8f14e",
"ClientSecret": "------------------------",
"Domain": "myCompanyPassport.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_myCompanySignUpSignIn",
"ReadScope": "read",
"WriteScope": "write"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
Did you try this ( addazureadbearer api )
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAd", options));

Log to Console with .Net Core 2.0 web application

I'm giving my first steps with .Net Core
Just created a web Hello world with
dotnet new web
I can see there's some kind of logging enabled. I just want to log something to the Console.
But I don't know how to access the logger from
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!!!");
});
I tried with Console.WriteLine but it obviously didn't work.
Also tried with NLog following this guide https://github.com/NLog/NLog.Web/wiki/Getting-started-with-ASP.NET-Core-(csproj---vs2017) but I don't know how to inject the logger.
I'm just trying to look around for educational purposes, not looking for a real logger, so perhaps there's a better/easier option.
I could achieve it with this:
[...]
using Microsoft.Extensions.Logging;
[...]
namespace web
{
public class Startup
{
ILogger log;
public Startup(ILoggerFactory loggerFactory)
{
log = loggerFactory.CreateLogger("Logger");
}
[...]
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole();
loggerFactory.AddDebug();
[...]
app.Run(async (context) =>
{
log.LogInformation("logging!");
await context.Response.WriteAsync("Hello World!");
});
}
}
}
also had to add an appsettings.json file to the root of the project
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

Resources