I'm trying to create a Blazor WASM application that will call a GRPC gateway using grpc-web.
The description of the Gateway Service is:
syntax = "proto3";
import "Services/AdService.proto";
package BonnieAndClydesdale.Core;
service GatewayService {
rpc GetAds (AdRequest) returns (AdReply);
}
I've followed this guide to set-up grpc-web on server and client.
I have this in the program.cs of my server:
builder.Services.AddGrpc();
builder.Services.AddGrpcReflection();
builder.Services.AddGrpcClient<AdService.AdServiceClient>(o => o.Address = new("https://localhost:7223")); // Downstream GRPC service
WebApplication app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<GatewayService>().EnableGrpcWeb();
app.UseGrpcWeb();
And this in the program.cs of my blazor app:
builder.Services.AddSingleton(services =>
{
HttpClient httpClient = new(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
string baseUri = "https://localhost:7080"; // TODO - Add to AppSettings
GrpcChannel channel = GrpcChannel.ForAddress(baseUri, new() { HttpClient = httpClient });
return new GatewayService.GatewayServiceClient(channel);
});
However, when I load this page:
public partial class ForSale: ComponentBase
{
[Inject]
private GatewayService.GatewayServiceClient Client { get; init; } = null!;//TODO replace with service
private readonly List<AdDetails> _ads;
public ForSale()
{
_ads = new();
}
protected override async Task OnInitializedAsync()
{
AdReply? rep = await Client.GetAdsAsync(new());
if (rep.AdDetails is not null)
{
_ads.AddRange(rep.AdDetails);
}
}
}
I'm getting a CORS error:
Access to fetch at
'https://localhost:7080/BonnieAndClydesdale.Core.GatewayService/GetAds'
from origin 'https://localhost:5001' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
Any ideas on how to approach fixing this?
EDIT
I've seen a lot of answers to similar questions that suggest using services.AddCors(...) and app.UseCors() but these methods don't seem to exist on Blazor WASM apps.
The issue arose from a misunderstanding as to where the CORS policy needed to be set. It needed to be set in the gateway server rather than on the Blazor WASM web-app. This makes sense since CORS is implemented on the server but the confusion arose because most tutorials seem to assume that we're using Blazor Server.
For the sake of completeness, I added this to the startup.cs of my gateway (not the Blazor App).
const string corsPolicy = "_corsPolicy";
builder.Services.AddCors(options =>
{
options.AddPolicy(name: corsPolicy,
policy =>
{
policy.WithOrigins("https://localhost:5001",
"http://localhost:5000")
.AllowAnyHeader()
.AllowAnyMethod();
});
});
WebApplication app = builder.Build();
app.UseCors(corsPolicy);
Related
I've been given a task to to build a web client that interacts with the following API:
https://docs.openaq.org/
It should be able to send different parameters to the API, and display the air quality results for a given city in a friendly manner.
I've chosen to build an Angular project in Asp.net core.
Code in program.cs
var builder = WebApplication.CreateBuilder(args);
var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors(options =>
{
options.AddPolicy(name: MyAllowSpecificOrigins,
policy =>
{
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
);
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
});
}
app.UseCors(MyAllowSpecificOrigins);
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
app.Component.ts code:
import { HttpClient } from '#angular/common/http';
import { error } from '#angular/compiler/src/util';
import { OnInit } from '#angular/core';
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'OpenAQ Application';
pings: any;
readonly ROOT_URL = "https://docs.openaq.org"
posts:any
constructor(private http: HttpClient) { }
async ngOnInit(): Promise<void> {
await this.http.get(this.ROOT_URL + '/ping').subscribe(response => {
this.pings = response;
}, error => {
console.log(error);
});
}
}
Error being received after launching my angular project.
Access to XMLHttpRequest at 'https://docs.openaq.org/ping' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Does anyone know why this is occurring?
This is happening because browsers work with same origin policy because of which they block response from servers which are from different origin(domain) than your request origin.
This is usually solved by sending Access-Control-Allow-Origin header from server in response, but in this case I see you are using 3rd party API which I guess you won't have control to modify. As you have your own backend you can send this request to your backend which can make this 3rd party request, retrieve the response and send it back to browser.
Solving CORS error from client is not possible but there are ways like proxy which can be used in development environments. I am not much familiar with Angular but I found this article which can help you : https://www.stackhawk.com/blog/angular-cors-guide-examples-and-how-to-enable-it/
I'm trying out OpenIddict 3.0 for use in a SSO app. I followed the steps in the documentation, created an Authorize controller, and added a test application. When I try to connect to authorize I get this exception:
System.InvalidOperationException: The authorization request was not handled. To handle authorization requests, create a class implementing 'IOpenIddictServerHandler' and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.
Alternatively, enable the pass-through mode to handle them at a later stage.
I can't find anything in the documentation or sample apps that explains what this means. What am I missing?
Here's my code so far. In Startup.cs:
services.AddOpenIddict()
.AddCore(o =>
{
o.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>();
})
.AddServer(o =>
{
o.SetTokenEndpointUris("/connect/token");
o.SetAuthorizationEndpointUris("/connect/authorize");
o.AllowAuthorizationCodeFlow();
o.RegisterScopes(OpenIddictConstants.Scopes.Email);
o.AcceptAnonymousClients();
o.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
o.UseAspNetCore()
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement();
})
.AddValidation(o =>
{
o.UseLocalServer();
o.UseAspNetCore();
});
And test app description:
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "test-app",
DisplayName = "Test Application",
PostLogoutRedirectUris = { new Uri("https://oidcdebugger.com/debug") },
RedirectUris = { new Uri("https://oidcdebugger.com/debug") }
};
I'm testing with the OpenID Connect debugger.
To handle authorization requests in a MVC controller, you must tell OpenIddict's ASP.NET Core host to use the pass-through mode, exactly like what you did for the token endpoint:
services.AddOpenIddict()
.AddServer(options =>
{
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough() // Add this line.
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement();
});
We are using ASPNETCore.SignalR 1.1.0 inside our Web API (netcoreapp2.2).
Authentication : We are using IdentityServer4 authentication for our project.
Startup.cs
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://IdentityServerDomainURL:8081/";
options.RequireHttpsMetadata = false;
options.ApiName = "name";
options.ApiSecret = "secret";
});
In WebAPI application we have added our SignalR Hub.
A JavaScript client connects to this Hub.
Following is the code of JS client for connecting to the hub.
var connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:52177/SignalRHub/",
{
accessTokenFactory: () => "referencetokenValue"
}).build();
The JS client is passing the Reference token at the time of connecting to the Hub.
We need to use Reference token authentication for SignalR in the WebAPI project.
In Microsoft`s site only JWT token authentication documentation for SignalR is provided. Did not find any document anywhere regarding Reference tokens.
Need help in adding the configuration for reference token authentication in startup.cs file.
Found the solution here.
Any token coming in query string can be added in the request headers as follows:-
app.Use(async (context, next) =>
{
if (context.Request.Path.Value.StartsWith("/SignalRHub/"))
{
var bearerToken = context.Request.Query["access_token"].ToString();
if (!String.IsNullOrEmpty(bearerToken))
context.Request.Headers.Add("Authorization", new string[] { "bearer " + bearerToken });
}
await next();
});
The above code has to be added in the Configure function of startup class.
I've made an Azure bot application using the BotFramework v4 and used the WebChat control as an interface. I noticed that the bot server's dotnetcore app had a wwwroot folder with a placeholder HTML page in it, so thought it might be expedient to host the webchat client there. But now seems counter-intuitive that my webchat client is using DirectLine to send activities back to the same back-end that served it.
I had chosen the webchat client because I need to customise the appearance of the client. I also need the MVC app that serves the bot client to include Azure Active Directory B2C authentication (which it does). Users should see the webchat client before and after authentication but the bot back-end (handling the activities) needs to know whether the user is logged in and modify its behaviour accordingly (and I am struggling to achieve that part with DirectLine).
So my first question (ever on StackOverflow) is: With the Bot back-end and the webchat client front-end being hosted in the same, single Azure web app, is it necessary to use DirectLine, or is there a simpler way of doing this?
Relevant code in my Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
app.UseStaticFiles(); // to allow serving up the JS, CSS, etc., files.
app.UseBotFramework(); // to add middleware to route webchat activity to the bot back-end code
app.UseSession(); // to enable session state
app.UseAuthentication(); // to enable authentication (in this case AAD B2C)
app.UseMvcWithDefaultRoute(); // to add MVC middleware with default route
}
Also in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// standard code to add HttpContextAssessor, BotServices, BotConfigs and memory storage singletons ommitted for brevity ...
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAdB2C(options => Configuration.Bind("Authentication:AzureAdB2C", options))
.AddCookie();
services.AddMvc();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromHours(1);
options.CookieHttpOnly = true;
});
// Create and add conversation state.
var conversationState = new ConversationState(dataStore);
services.AddSingleton(conversationState);
var userState = new UserState(dataStore);
services.AddSingleton(userState);
services.AddBot<MyBot>(options =>
{
options.CredentialProvider = new SimpleCredentialProvider(endpointService.AppId, endpointService.AppPassword);
options.ChannelProvider = new ConfigurationChannelProvider(Configuration);
// Catches any errors that occur during a conversation turn and logs them to currently
// configured ILogger.
ILogger logger = _loggerFactory.CreateLogger<RucheBot>();
options.OnTurnError = async (context, exception) =>
{
logger.LogError($"Exception caught : {exception}");
await context.SendActivityAsync("Sorry, it looks like something went wrong.");
};
});
}
My controller's Index method:
public async Task<ActionResult> Index()
{
string userId;
if (User.Identity.IsAuthenticated)
{
string aadb2cUserId = User.FindFirst("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier").Value;
Users.EnsureAccountExists(aadb2cUserId); // ensure account with given AAD identifier is know locally (by creating it if not)
userId = $"ia_{aadb2cUserId}";
}
else
{
userId = $"na_{Guid.NewGuid()}";
}
HttpClient client = new HttpClient();
string directLineUrl = $"https://directline.botframework.com/v3/directline/tokens/generate";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, directLineUrl);
// TODO: put this in the config somewhere
var secret = "<the secret code from my bot's DirectLine channel config in the Azure portal>";
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secret);
string jsonUser = JsonConvert.SerializeObject(new { User = new { Id = userId } });
request.Content = new StringContent(jsonUser, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
string token = string.Empty;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
token = JsonConvert.DeserializeObject<DirectLineToken>(body).token;
}
var config = new ChatConfig()
{
Token = token,
UserId = userId,
};
return View(config);
}
And finally the code in the associated view:
<div id="webchat"></div>
<script type="text/javascript">
...
/// Called asynchronously during the page load
function renderWebChat( withSound )
{
var webchatOptions =
{
directLine: window.WebChat.createDirectLine( { secret: '#Model.Token'} ),
userID: '#Model.UserId'
};
if ( withSound )
{
webchatOptions.webSpeechPonyfillFactory = window.WebChat.createBrowserWebSpeechPonyfillFactory();
}
window.WebChat.renderWebChat( webchatOptions, document.getElementById( 'webchat' ) );
document.querySelector( '#webchat > *' ).focus();
}
</script>
I'm going to disagree with Nicolas R a little bit. When it comes to directly accessing your bot, you might like to have a look at this: https://www.npmjs.com/package/offline-directline
There's also the option of hosting a bot in the browser, which I think may facilitate the sort of direct communication you're looking for.
Long question, but the answer will be a lot shorter!
So my first question (ever on StackOverflow) is: With the Bot back-end
and the webchat client front-end being hosted in the same, single
Azure web app, is it necessary to use DirectLine, or is there a
simpler way of doing this?
Yes, it is necessary. In fact, all the channels types are using the Bot Connector to communicate with your backend (your bot code), there is no direct access possible. There are a lot of reasons for that, one is for example the billing!
We have an ASP.NET MVC application that is authenticating without issue against IdentityServer3, however the web API part of the application using ApiController's start to fail if the user waits before proceeding with AJAX functionality after about 3 minutes (before 3 mins everything seems fine).
The errors seen in Chrome are:
XMLHttpRequest cannot load
https://test-auth.myauthapp.com/auth/connect/authorize?client_id=ecan-farmda…gwLTk5ZjMtN2QxZjUyMjgxNGE4MDg2NjFhZTAtOTEzNi00MDE3LTkzNGQtNTc5ODAzZTE1Mzgw.
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://test.myapp.com' is therefore not allowed
access.
On IE I get the following errors:
SCRIPT7002: XMLHttpRequest: Network Error 0x4c7, The operation was
canceled by the user.
Looking at IdentityServer3's logs I'm seeing entries like so:
2015-08-10 16:42 [Warning]
(Thinktecture.IdentityServer.Core.Configuration.Hosting.CorsPolicyProvider)
CORS request made for path: /connect/authorize from origin:
http://test.myapp.com but rejected because invalid CORS path
In the IdentityServer3 web application I'm giving clients AllowedCorsOrigins:
Thinktecture.IdentityServer.Core.Models.Client client = new Thinktecture.IdentityServer.Core.Models.Client()
{
Enabled = configClient.Enabled,
ClientId = configClient.Id,
ClientName = configClient.Name,
RedirectUris = new List<string>(),
PostLogoutRedirectUris = new List<string>(),
AllowedCorsOrigins = new List<string>(),
RequireConsent = false, // Don't show consents screen to user
RefreshTokenExpiration = Thinktecture.IdentityServer.Core.Models.TokenExpiration.Sliding
};
foreach (Configuration.RegisteredUri uri in configClient.RedirectUris)
{
client.RedirectUris.Add(uri.Uri);
}
foreach (Configuration.RegisteredUri uri in configClient.PostLogoutRedirectUris)
{
client.PostLogoutRedirectUris.Add(uri.Uri);
}
// Quick hack to try and get CORS working
client.AllowedCorsOrigins.Add("http://test.myapp.com");
client.AllowedCorsOrigins.Add("http://test.myapp.com/"); // Don't think trailing / needed, but added just in case
clients.Add(client);
And when registering the service I add a InMemoryCorsPolicyService:
app.Map("/auth", idsrvApp =>
{
var factory = new IdentityServerServiceFactory();
factory.Register(new Registration<AuthContext>(resolver => AuthObjects.AuthContext));
factory.Register(new Registration<AuthUserStore>());
factory.Register(new Registration<AuthRoleStore>());
factory.Register(new Registration<AuthUserManager>());
factory.Register(new Registration<AuthRoleManager>());
// Custom user service used to inject custom registration workflow
factory.UserService = new Registration<IUserService>(resolver => AuthObjects.AuthUserService);
var scopeStore = new InMemoryScopeStore(Scopes.Get());
factory.ScopeStore = new Registration<IScopeStore>(scopeStore);
var clientStore = new InMemoryClientStore(Clients.Get());
factory.ClientStore = new Registration<IClientStore>(clientStore);
var cors = new InMemoryCorsPolicyService(Clients.Get());
factory.CorsPolicyService = new Registration<ICorsPolicyService>(cors);
...
var options = new IdentityServerOptions
{
SiteName = "Authentication",
SigningCertificate = LoadCertificate(),
Factory = factory,
AuthenticationOptions = authOptions
};
...
});
I do note that the IdentityServer3 log entries say "CORS request made for path: /connect/authorize" rather than "CORS request made for path: /auth/connect/authorize". But looking through the IdentityServer3 source code suggests this probably isn't the issue.
Perhaps the InMemoryCorsPolicyService isn't being picked up?
Any ideas of why things aren't working for the AJAX called ApiController?
Thinktecture.IdevtityServer3 v1.6.2 has been installed using NuGet.
Update
I'm having a conversation with the IdentityServer3 developer, but am still having an issue reaching a resolution. In case it helps:
https://github.com/IdentityServer/IdentityServer3/issues/1697
Did you try adding https url also?- client.AllowedCorsOrigins.Add("https://test.myapp.com");
The documentation of IdentityServer says you should configure it on the client:
AllowedCorsOrigins = ... // Defaults to the discovery, user info, token, and revocation endpoints.
https://docs.duendesoftware.com/identityserver/v6/reference/options/#cors
CORS is a nightmare!
It's a browser thing which is why you're witnessing different behaviour in IE than in Chrome.
There are (at least) two ways that CORS is configured on the server. When a client makes a request with the Origin header you have to tell the server whether or not to accept it -- if accepted then the server adds the Access-Control-Allow-Origin header to the response for the browser.
In MVC / webAPI you have to add CORS services, set a CORS policy, and then .UseCors something like this:
builder.Services.AddCors((options =>
{
if (settings.AllowedCorsOrigins.Length > 0)
{
options.AddDefaultPolicy(builder =>
{
builder.SetIsOriginAllowedToAllowWildcardSubdomains();
builder.AllowAnyHeader().AllowAnyMethod().WithOrigins(settings.AllowedCorsOrigins);
});
}
if (isDevelopment)
{
options.AddPolicy("localhost", builder =>
{
builder.SetIsOriginAllowedToAllowWildcardSubdomains();
builder.AllowAnyHeader().AllowAnyMethod().SetIsOriginAllowed((string origin) => { return origin.Contains("localhost"); }); });
}
});
and
app.UseCors();
if (app.Environment.IsDevelopment())
{
app.UseCors("localhost");
}
Typically, you want the list of allowed hosts as an array of strings in your appsettings.json. And watch out for the boobytrap with SetIsOriginAllowedToAllowWildcardSubdomains.
As well as this, IdentityServer has its own additional CORS settings which are applied in addition to the standard MVC/webAPI settings. These are in the ClientCorsOrigin table and this doesn't support wildcard subdomains. You can sidestep this whole boobytrap by implementing your own ICorsPolicyService to use the same settings from your appsettings.json something like this
public class CorsPolicyService : ICorsPolicyService
{
private readonly CorsOptions _options;
public CorsPolicyService(IOptions<CorsOptions> options)
{
_options = options.Value;
}
private bool CheckHost(string host)
{
foreach (string p in _options.AllowedCorsOrigins)
{
if (Regex.IsMatch(host, Regex.Escape(p).Replace("\\*", "[a-zA-Z0-9]+"))) // Hyphen?
{
return true;
}
}
return false;
}
public Task<bool> IsOriginAllowedAsync(string origin)
{
return Task.FromResult(CheckHost(origin));
}
}