Blazor server and GRPC server in the same app - grpc

I am trying to add GRPC server capability to an existing Blazor server app. The intention is that the app will provide a web interface for human operation and also a GRPC server for access by other GRPC client apps. I have been through tutorials and docs on .NET GRPC and I understand the concepts. What I do not understand is how, or even if, the Kestrel server can be configured to do this.
To the Blazor app I have added what I think is needed to implement the basic “Greeter” service that comes with the Visual Studio “gRPC Service” project template. The app builds and runs, but when I try to access the Blazor web site from the browser I get the “Communication with gRPC endpoints must be made through a gRPC client” message which tells me that the GRPC server is now working but the Blazor server is now not working.
Is it possible to configure different URLs for the Blazor and GRPC bits, and if so how? Or is there something else I should be doing? Any ideas will be much appreciated.
EDIT
The following shows the bits of code I edited to add the gRPC stuff:
The csproj file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<Version>1.0.3</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.29.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\S34021.Edm\S34021.Edm.csproj" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
</ItemGroup>
</Project>
The appsettings.json file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
}
The Startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddGrpc();
}
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.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
// Added for GRPC
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}

Related

How to use Azure AD with OpenId Connect in a iFrame

I've just been looking for hours for a way to call a web app in an iFrame that is protected with OpenId Connect SSO.
Current status is that either the following message appears with the link https://localhost:5001/signin-oidc.
Or the following error message with the link https://localhost:5001 :
If the application is called directly via the browser, then the login screen of Azure appears as desired. Only when I go via the iFrame, nothing works anymore. Even if I open the application first and log in, it does not work with iFrame.
The settings were made with the default template of Rider
csproj
...
<UserSecretsId>SomeGuideWithName</UserSecretsId>
<WebProject_DirectoryAccessLevelKey>0</WebProject_DirectoryAccessLevelKey>
...
App.razor
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)"/>
<FocusOnNavigate RouteData="#routeData" Selector="h1"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
appsettings.json
xxx --> Secreds that are correct.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "xxx.onmicrosoft.com",
"TenantId": "xxx",
"ClientId": "xxx",
"CallbackPath": "/signin-oidc"
}
Program.cs
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using MitAuth.Data;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(
options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
}
);
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
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.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
Generated Cookies
The goal is to have application A (Caller) embed an iFrame that application B (OpenIdConnect protected).
Questions:
Do I need to reconfigure anything in App B?
Do I need to do anything else in App A?
Is there any token or similar that has to be passed on?
I tried to reproduce the scenario in my environment:
I got the same error:
System.Exception: An error was encountered while handling the remote login.
---> System.Exception: OpenIdConnectAuthenticationHandler: message.State is null or empty.
Here due to misconfiguration in callback path I got the error.
In your case check without giving callbackpath in both code and portal.
Appsettings.json:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mytenantxxx.onmicrosoft.com",
"TenantId": "fbxxxxxxxxxxxxf3b0",
"ClientSecret": "xxxx",
"ClientId": "xxxx",
"CallbackPath": "/"
},
I have configured following redirect urls in portal.
My application url uses http protocol http://localhost:44328
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
services.AddRazorPages();
services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
services.AddSingleton<WeatherForecastService>();
}
// 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();
IdentityModelEventSource.ShowPII = true;
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
When I changed the CallbackPath value to /signin-oidc , that error was resolved.
Appsettings.json:
"AzureAd": {
...
"CallbackPath": "/signin-oidc"
},
Also check Exception: Correlation failed. AAD + Azure Front Door

How do I host a WebApplication and a BackgroundService in the same application?

I have a command-line application (similar to what would be created with the dotnet new worker command) that hosts a BackgroundService implementation, like this:
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services => {
services.AddHostedService<MyBackgroundService>();
services.AddSingleton<IMyType>(myinstance);
// and so on...
}).Build();
await host.RunAsync();
Now, I would like this application to also host a web api. Currently, I have a whole separate builder that I'm instantiating inside the MyBackgroundService class, with a separate set of services/singletons/whatever:
var builder = WebApplication.CreateBuilder();
// ... various web stuff...
var webApi = builder.Build();
// ... more web api stuff...
await webApi.StartAsync(cancellationToken);
I'd like to set this up to share a single DI container... how do I do that? My web api uses a WebApplicationBuilder, while my BackgroundService uses a DefaultBuilder (which is a IHostBuilder). Is there any way to set this up elegantly?
Edit: I found this: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-6.0 which gives me the ConfigureWebHostDefaults method for the generic host, but the example isn't complete. I can put the AddControllers() and AddRouting() etc on the generic host, but once I call Build() I can't do things like UseHttpLogging() or UseSwagger() or MapControllers().
Assuming you're starting from a console app and want to add in the API portion, you can do something like:
await Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<SomeBackgroundService>();
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureServices(services =>
{
services.AddControllers();
services.AddEndpointsApiExplorer();
services.AddSwaggerGen();
})
.Configure((hostContext, app) =>
{
if (hostContext.HostingEnvironment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
​
app.UseHttpsRedirection();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
//can optionally use a startup file similar to what older versions of .NET used
//webBuilder.UseStartup<Startup>();
})
.Build()
.RunAsync();
}
Also, assuming you started from a console app, you may need to add:
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
to your csproj, as that will pull in the additional web options.
Edit: It appears the services specific to the web context need to be registered within the webBuilder.

Can't open Swagger UI outside of visual studio debug

I've created a very basic .net CORE WEB API, following a youtube guide.
Everything works fine when I debug from visual studio. it opens my browser and directs me to the swagger ui site.
But when I run outside visual studio, starting the exe from the build/bin folder I can't get to that swagger site.
in a command propt the exe file says it is listening on http on port 5000.
so while this is running go to this url http://localhost:5000/swagger/index.html
anyone have a hit on what I'm missing here ?
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection": "server=myServerXXXX;database=myDataBaseXXXX;User ID=mydbUserXXXXX;Password=myPasswordXXX"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
program.cs
global using ShoppingList.API.Data;
global using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddDbContext<ShoppingItemDataContext>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"));
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseAuthorization();
app.MapControllers();
app.Run();
By default dotnet runs applications with the production environment and you have swagger enabled for the dev environment only. change the condition or pass the "--environment production" argument (or configure host environment: e.g. https://enlabsoftware.com/development/dotnet-core-environment-how-config-examples.html)

Does not start ASP.net service with SPA (React)

Please help me launch the web service. I run the .exe file from the folder "\bin\Debug\net 5.0". When prompted https://localhost:5001/ gives an error.
I specified it in the spa.Options.sourcepath, but it didn't help.
the ERROR that I received is the following:
fail: Microsoft.AspNetCore.Server.Kestrel[13]
Connection id "0HMD866NK2DDC", Request id "0HMD866NK2DDC:00000011": An unhandled exception was thrown by the application.
System.InvalidOperationException: The SPA default page middleware could not return the default page '/index.html' because it was not found, and no other middleware handled the request.
Your application is running in Production mode, so make sure it has been published, or that you have built your SPA manually. Alternatively you may wish to switch to the Development environment.
This is the folder structure ClientApp Paths
My config code:
public void ConfigureServices(IServiceCollection services)
{
// получаем строку подключения из файла конфигурации
string connection = Configuration.GetConnectionString("DefaultConnection");
// добавляем контекст ApplicationContext в качестве сервиса в приложение
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(connection));
//чтобы кирилица нормально отображалась
services.AddWebEncoders(o =>
{
o.TextEncoderSettings = new TextEncoderSettings(UnicodeRanges.BasicLatin, UnicodeRanges.Cyrillic, UnicodeRanges.CyrillicExtendedA, UnicodeRanges.CyrillicExtendedB);
});
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.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
React assets are not built by dotnet build or dotnet run not even with -c Release.
To also build the React assets you need dotnet publish.
This requirement may be masked by the presence of assets due to earlier use of dotnet publish. In that case they are potentially stale. Therefore, for any environment other than dev, you must use dotnet publish.
Why don't dotnet build and dotnet run build the React assets? In development you don't need them because you're using the dev server for hot swap. In production you're almost certainly using assets prepared by dotnet publish.
AspNetCore React can only find spa files in dev mode

ASP.net Core 2.0 AzureAd Bearer JWT-Token Auth not fail on validate signature

I have two Apps one Web App (Web-UI) and a web api.
First I implemented into the Web-UI an Authentication with Azure AD.
Both apps are registered in Azure and thrusting each other.--
First my Config
tenant: '99c7da52-56fc-49ca-aa95-111111111111',
clientId: '6b0a79eb-0e0d-4a00-9652-111111111111
The Login works like a charme I get successfully a Token. In this it looks like the following:
Header
{
"typ": "JWT",
"nonce": "AQABAAAAAABHh4kmS_aKT5XrjzxRAtHzn-GbcsmT8MupNislUn7vudKeuWR-HgBEd2ceWxQ7UulHr-uachkZA9cWVIj5ah3yzI68oYKyzc-QdynAf3a5DSAA",
"alg": "RS256",
"x5t": "z44wMdHu8wKsumrbfaK98qxs5YI",
"kid": "z44wMdHu8wKsumrbfaK98qxs5YI"
}
Payload
{
"aud": "00000003-0000-0000-c000-000000000000",
"iss": "https://sts.windows.net/99c7da52-56fc-49ca-aa95-8f7fb09c995e/",
"iat": 1517145610,
"nbf": 1517145610,
"exp": 1517149510,
"acr": "1",
"aio": "ATQAy/8GAAAAYOQzoNWpu5XcyTPibpz9lnb/bMGY3H4iTdEdz/zvWwrTt1mvvWiLToaNPYZwHsBD",
"amr": [
"pwd",
"mfa"
],
"app_displayname": "My Project",
"appid": "6b0a79eb-0e0d-4a00-9652-3098cc95804f",
"appidacr": "0",
"e_exp": 262800,
"family_name": "XXXXXXXXX",
"given_name": "XXXXXXXXXX",
"ipaddr": "93.245.65.135",
"name": "Myname, Firstname",
"oid": "1a4d0d0f-8137-4c1d-aa34-2cccf10f8206",
"platf": "3",
"puid": "100300008AAF747A",
"scp": "Directory.Read.All email Group.Read.All profile User.Read User.Read.All User.ReadBasic.All",
"sub": "IBv63_IIq4zpkv_UlVwaxmAm0RP3d17xq4hKil4HRD0",
"tid": "89c7da52-56fc-49ca-aa95-8f7fb09c995e",
"unique_name": "XXXXXXXXXXXXXXX",
"upn": "XXXXXXXXXXXXXXX",
"uti": "LVVtGIEcXUuEofBatBYVAA",
"ver": "1.0"
}
I removed the persnal information. So now I use the Token against my Web.Api this contains the following Setting:
"AzureAdB2C": {
"Instance": "https://login.microsoftonline.com/common",
"ClientId": "f5afed6b-e09c-4c7d-90cc-222222222222",
"Domain": "myhost.de"
}
My Startup File looks:
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(sharedOptions =>
{
sharedOptions.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddAzureAdBearer(options => Configuration.Bind("AzureAdB2C", options));
services.AddMvc();
}
// 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.UseAuthentication();
app.UseMvc();
}
}
No when I make a call to the api with Postman with the same token above as "Bearer {tokenstring}". It tells me the 401 not Authorized.
In the Debug log on the console comes this up:
Microsoft.IdentityModel.Tokens.SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: z44wMdHu8wKsumrbfaK98qxs5YI
'.
Exceptions caught:
''.
token: '{"alg":"RS256","typ":"JWT","nonce":"AQABAAAAAABHh4kmS_aKT5XrjzxRAtHzn-GbcsmT8MupNislUn7vudKeuWR-HgBEd2ceWxQ7UulHr-uachkZA9cWVIj5ah3yzI68oYKyzc-QdynAf3a5DSAA","x5t":"z44wMdHu8wKsumrbfaK98qxs5YI","kid":"z44wMdHu8wKsumrbfaK98qxs5YI"}.{"aud":"00000003-0000-0000-c000-000000000000","iss":"https://sts.windows.net/99c7da52-56fc-49ca-aa95-8f7fb09c995e/","iat":1517145610,"nbf":1517145610,"exp":1517149510,"acr":"1","aio":"ATQAy/8GAAAAYOQzoNWpu5XcyTPibpz9lnb/bMGY3H4iTdEdz/zvWwrTt1mvvWiLToaNPYZwHsBD","amr":["pwd","mfa"],"app_displayname":"Project Avalon Dev","appid":"6b0a79eb-0e0d-4a00-9652-3098cc95804f","appidacr":"0","e_exp":262800,"family_name":"XXXXX","given_name":"Sascha Peter","ipaddr":"93.245.65.135","name":"XXXX","oid":"da4d0d0f-8137-4c1d-aa34-2cccf10f8206","platf":"3","puid":"100300008AAF747A","scp":"Directory.Read.All email Group.Read.All profile User.Read User.Read.All User.ReadBasic.All","sub":"IBv63_IIq4zpkv_UlVwaxmAm0RP3d17xq4hKil4HRD0","tid":"99c7da52-56fc-49ca-aa95-8f7fb09c995e","unique_name":"XXXXX","upn":"xxxxxxx","uti":"LVVtGIEcXUuEofBatBYVAA","ver":"1.0"}'.
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleAuthenticateAsync>d__6.MoveNext()
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[7]
Bearer was not authenticated. Failure message: IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.X509SecurityKey , KeyId: z44wMdHu8wKsumrbfaK98qxs5YI
'.
Exceptions caught:
''.
token: '{"alg":"RS256","typ":"JWT","nonce":"AQABAAAAAABHh4kmS_aKT5XrjzxRAtHzn-GbcsmT8MupNislUn7vudKeuWR-HgBEd2ceWxQ7UulHr-uachkZA9cWVIj5ah3yzI68oYKyzc-QdynAf3a5DSAA","x5t":"z44wMdHu8wKsumrbfaK98qxs5YI...........
My Projectfile for the webapi project like this:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<UserSecretsId>aspnet-Portal.Api-B6631B80-5958-40CC-A783-2E86D5ADA6B5</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.5" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.2" />
</ItemGroup>
</Project>
I don't know what's the problem with this ist. Do I missing something? Is there a Bug in my Code?
the aspnetcore jwt bearer implementation cannot validate tokens with a nonce in the header while this needs special processing. The token you are trying to validate is for the microsoft graph api, not for your application.
you can obtain tokens for your app if you request https://login.microsoftonline.com/common/oauth2/authorize?resource={APPID}
i was also able to validate tokens for resource/api https://graph.windows.net (instead of https://graph.microsoft.com) as this tokens does not contain nonces, but this api is according to microsoft outdated and should not be used (https://dev.office.com/blogs/microsoft-graph-or-azure-ad-graph).
read more:
https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/609

Resources