Trying to create a FirebaseDb object in C# (Blazor WASM) is throwing a Blocked mixed content error after it's deployed to Firebase. Is there a way to force this to use HTTPS?
The errror:
Blocked loading mixed active content “http://169.254.169.254/”
dotnet.3.2.0-preview3.20168.1.js:1:163131
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100] blazor.webassembly.js:1:36074
Unhandled exception rendering component: TypeError: NetworkError when attempting to fetch resource. blazor.webassembly.js:1:36074
WebAssembly.JSException: TypeError: NetworkError when attempting to fetch resource. blazor.webassembly.js:1:36074
at WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler.doFetch (System.Threading.Tasks.TaskCompletionSource1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x257c7e0 + 0x00988> in <filename unknown>:0 blazor.webassembly.js:1:36074
at WebAssembly.Net.Http.HttpClient.WasmHttpMessageHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x255e648 + 0x00184> in <filename unknown>:0 blazor.webassembly.js:1:36074
at System.Net.Http.HttpClient.FinishSendAsyncBuffered (System.Threading.Tasks.Task
1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) <0x256c970 + 0x00278> in :0 blazor.webassembly.js:1:36074
at Google.Apis.Auth.OAuth2.ComputeCredential.IsRunningOnComputeEngineNoCache () <0x24f5570 + 0x0018c> in :0 blazor.webassembly.js:1:36074
at Google.Apis.Auth.OAuth2.DefaultCredentialProvider.CreateDefaultCredentialAsync () <0x24e22f0 + 0x0020e> in :0 blazor.webassembly.js:1:36074
at Google.Api.Gax.Grpc.ChannelPool.CreateChannelCredentialsUncached () <0x24cf210 + 0x000d8> in :0 blazor.webassembly.js:1:36074
at Google.Api.Gax.Grpc.ChannelPool.GetChannelAsync (Google.Api.Gax.Grpc.ServiceEndpoint endpoint, System.Collections.Generic.IEnumerable`1[T] channelOptions) <0x246fb30 + 0x000f4> in :0 blazor.webassembly.js:1:36074
at Google.Cloud.Firestore.V1.FirestoreClient.CreateAsync (Google.Api.Gax.Grpc.ServiceEndpoint endpoint, Google.Cloud.Firestore.V1.FirestoreSettings settings) <0x246e908 + 0x000ec> in :0 blazor.webassembly.js:1:36074
at Google.Cloud.Firestore.FirestoreDb.CreateAsync (System.String projectId, Google.Cloud.Firestore.V1.FirestoreClient client) <0x2449d00 + 0x001d0> in :0 blazor.webassembly.js:1:36074
at blog.Pages.Index.OnInitializedAsync () <0x2434eb8 + 0x000c8> in :0 blazor.webassembly.js:1:36074
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x2330b40 + 0x0014c> in :0 blazor.webassembly.js:1:36074
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask (System.Threading.Tasks.Task taskToHandle) <0x247d5b8 + 0x000c2> in :0 blazor.webassembly.js:1:36074
The code:
protected override async Task OnInitializedAsync()
{
string projectId = "my-poject-id";
FirestoreDb db = await FirestoreDb.CreateAsync(projectId);
}
TL;DR
The Firestore library is not supposed to be run within the browser context. It's developed to be used server-side or maybe in an admin tool. The library will allow you to do everything in Firestore and shipping the credentials to customers would be a security risk.
Based on your stacktrace I'm assuming you're using the Google.Cloud.Firestore
library.
Unfortunately, this library is designed to be a "server client library". A client library to be used server side.
These "server client libraries" are designed differently than the "mobile/web client libraries".
The mobile/web libraries will use Firebase's authentication (username/password, Facebook, etc) and the security model will be applied on that user context.
The server libraries essentially give you access to everything.
Now the issues you're running into are caused by this SDK not being supported on Blazor.
First of all, the GOOGLE_APPLICATION_CREDENTIALS
environment variable that the SDK relies on won't be available inside your Blazor application.
If it were available, the file wouldn't be accessible from within the browser sandbox.
You can circumvent both issues by using the FirestoreDbBuilder
and setting the JsonCredentials
and ProjectId
manually like this:
var builder = new FirestoreDbBuilder();
builder.JsonCredentials = "{\"type\": \"service_account\", \"project_id\": \"\", \"private_key_id\": \"\", \"private_key\": \"\", \"client_email\": \"\", \"client_id\": \"\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"\" }";
builder.ProjectId = "";
FirestoreDb db = builder.Build();
This will result into the next issue, the GRPC library used doesn't support Blazor/.NET WASM resulting in this stacktrace:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Unsupported platform.
System.InvalidOperationException: Unsupported platform.
at Grpc.Core.Internal.NativeExtension.GetNativeLibraryFilename () [0x0003f] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:231
at Grpc.Core.Internal.NativeExtension.LoadUnmanagedLibrary () [0x0000a] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:93
at Grpc.Core.Internal.NativeExtension.LoadNativeMethods () [0x0001a] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:120
at Grpc.Core.Internal.NativeExtension..ctor () [0x00006] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:40
at Grpc.Core.Internal.NativeExtension.Get () [0x00022] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeExtension.cs:65
at Grpc.Core.Internal.NativeMethods.Get () [0x00000] in T:\src\github\grpc\src\csharp\Grpc.Core\Internal\NativeMethods.cs:49
at Grpc.Core.GrpcEnvironment.GrpcNativeInit () [0x00016] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:373
at Grpc.Core.GrpcEnvironment..ctor () [0x0001e] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:302
at Grpc.Core.GrpcEnvironment.AddRef () [0x00028] in T:\src\github\grpc\src\csharp\Grpc.Core\GrpcEnvironment.cs:78
at Grpc.Core.Channel..ctor (System.String target, Grpc.Core.ChannelCredentials credentials, System.Collections.Generic.IEnumerable`1[T] options) [0x00041] in T:\src\github\grpc\src\csharp\Grpc.Core\Channel.cs:70
at Google.Api.Gax.Grpc.GrpcCore.GrpcCoreAdapter.CreateChannelImpl (System.String endpoint, Grpc.Core.ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options) [0x00000] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc.GrpcCore\GrpcCoreAdapter.cs:34
at Google.Api.Gax.Grpc.GrpcAdapter.CreateChannel (System.String endpoint, Grpc.Core.ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options) [0x00024] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\GrpcAdapter.cs:29
at Google.Api.Gax.Grpc.ClientBuilderBase`1[TClient].CreateChannel (System.String endpoint, Grpc.Core.ChannelCredentials credentials) [0x00000] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ClientBuilderBase.cs:423
at Google.Api.Gax.Grpc.ClientBuilderBase`1[TClient].CreateCallInvokerAsync (System.Threading.CancellationToken cancellationToken) [0x00145] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ClientBuilderBase.cs:313
at Google.Cloud.Firestore.V1.FirestoreClientBuilder.BuildAsyncImpl (System.Threading.CancellationToken cancellationToken) [0x00033] in /_/apis/Google.Cloud.Firestore.V1/Google.Cloud.Firestore.V1/FirestoreClient.g.cs:285
at Google.Cloud.Firestore.FirestoreDbBuilder.BuildAsync (System.Threading.CancellationToken cancellationToken) [0x0015a] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDbBuilder.cs:119
at BlazorFirestore.Pages.FetchData.OnInitializedAsync () [0x00061] in C:\Users\niels\source\repos\BlazorFirestore\Pages\FetchData.razor:12
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x2ff3be8 + 0x0013a> in <filename unknown>:0
This can be resolved by using Grpc.Net.Client.Web
which is a Grpc implementation for .NET that works inside Blazor.
Create the following class (inspired on Google's Source):
using System;
using Grpc.Core;
using Google.Api.Gax.Grpc;
using Grpc.Net.Client;
using System.Net.Http;
using Grpc.Net.Client.Web;
// you'll need these packages
// Google.Apis.Auth, Google.Cloud.Firestore, Grpc.Net.Client, Grpc.Net.Client.Web
namespace BlazorFirestore
{
// most of the code was borrowed from https://github.com/googleapis/gax-dotnet/blob/master/Google.Api.Gax.Grpc.GrpcNetClient/GrpcNetClientAdapter.cs
public sealed class GrpcWebAdapter : GrpcAdapter
{
// this HttpClient using the GrpcWebHandler and mode is crucial to get Grpc to work for Firestore
private static HttpClient httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
// Note: this is "Default" rather than "Instance" as we expect to have other factory methods later, e.g. accepting
// an HTTP client factory.
/// <summary>
/// Returns the default instance of this class.
/// </summary>
public static GrpcWebAdapter Default { get; } = new GrpcWebAdapter();
private GrpcWebAdapter()
{
}
/// <inheritdoc />
protected override ChannelBase CreateChannelImpl(string endpoint, ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options)
{
var grpcNetClientOptions = ConvertOptions(credentials, options);
var address = ConvertEndpoint(endpoint);
return GrpcChannel.ForAddress(address, grpcNetClientOptions);
}
// Internal for testing
internal static global::Grpc.Net.Client.GrpcChannelOptions ConvertOptions(ChannelCredentials credentials, Google.Api.Gax.Grpc.GrpcChannelOptions options)
{
// If service config resolution is explicitly enabled, throw - we can't support that,
// and users may be depending on it.
if (options.EnableServiceConfigResolution == true)
{
throw new ArgumentException($"{nameof(options.EnableServiceConfigResolution)} is not currently supported in {nameof(GrpcWebAdapter)}");
}
if (options.CustomOptions.Count > 0)
{
throw new ArgumentException($"Custom options are not currently supported in {nameof(GrpcWebAdapter)}");
}
// Options we ignore:
// - PrimaryUserAgent
// - KeepAliveTime
return new global::Grpc.Net.Client.GrpcChannelOptions
{
Credentials = credentials,
MaxReceiveMessageSize = options.MaxReceiveMessageSize,
MaxSendMessageSize = options.MaxSendMessageSize,
// pass the GrpcWeb version of the httpclient
HttpClient = httpClient
};
}
// Internal for testing
internal static string ConvertEndpoint(string endpoint) =>
// Note that we assume HTTPS for any bare address; this feels like a reasonable assumption for now.
endpoint.StartsWith("http:", StringComparison.Ordinal) || endpoint.StartsWith("https:", StringComparison.Ordinal)
? endpoint : $"https://{endpoint}";
}
}
Now set GrpcWebAdapter.Default
to the FirestoreDbBuilder.GrpcAdapter
:
var builder = new FirestoreDbBuilder();
builder.JsonCredentials = "{\"type\": \"service_account\", \"project_id\": \"\", \"private_key_id\": \"\", \"private_key\": \"\", \"client_email\": \"\", \"client_id\": \"\", \"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\", \"token_uri\": \"https://oauth2.googleapis.com/token\", \"auth_provider_x509_cert_url\": \"https://www.googleapis.com/oauth2/v1/certs\", \"client_x509_cert_url\": \"\" }";
builder.ProjectId = "";
builder.GrpcAdapter = GrpcWebAdapter.Default;
FirestoreDb db = builder.Build();
DocumentReference docRef = db.Collection("users").Document("alovelace");
Dictionary<string, object> user = new Dictionary<string, object>
{
{ "First", "Ada" },
{ "Last", "Lovelace" },
{ "Born", 1815 }
};
await docRef.SetAsync(user);
We get one step closer, but now we run into CORS security issues since this way of communicating with Firestore wasn't designed to run from a browser and there's no way to configure CORS combined with this SDK. These are the resulting errors:
Access to fetch at 'https://firestore.googleapis.com/google.firestore.v1.Firestore/Commit' 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.
dotnet.3.2.0.js:1 POST https://firestore.googleapis.com/google.firestore.v1.Firestore/Commit net::ERR_FAILED
blazor.webassembly.js:1 crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Status(StatusCode="Internal", Detail="Error starting gRPC call. JSException: TypeError: Failed to fetch", DebugException="WebAssembly.JSException: TypeError: Failed to fetch
at System.Net.Http.WebAssemblyHttpHandler.doFetch (System.Threading.Tasks.TaskCompletionSource`1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c61b60 + 0x00a30> in <filename unknown>:0
at System.Net.Http.WebAssemblyHttpHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c24040 + 0x00174> in <filename unknown>:0
at Grpc.Net.Client.Web.GrpcWebHandler.SendAsyncCore (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00092] in /_/src/Grpc.Net.Client.Web/GrpcWebHandler.cs:137
at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) <0x3d9ae50 + 0x00134> in <filename unknown>:0
at Grpc.Net.Client.Internal.GrpcCall`2[TRequest,TResponse].RunCall (System.Net.Http.HttpRequestMessage request, System.Nullable`1[T] timeout) [0x0020c] in /_/src/Grpc.Net.Client/Internal/GrpcCall.cs:477 ")
Grpc.Core.RpcException: Status(StatusCode="Internal", Detail="Error starting gRPC call. JSException: TypeError: Failed to fetch", DebugException="WebAssembly.JSException: TypeError: Failed to fetch
at System.Net.Http.WebAssemblyHttpHandler.doFetch (System.Threading.Tasks.TaskCompletionSource`1[TResult] tcs, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c61b60 + 0x00a30> in <filename unknown>:0
at System.Net.Http.WebAssemblyHttpHandler.SendAsync (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) <0x3c24040 + 0x00174> in <filename unknown>:0
at Grpc.Net.Client.Web.GrpcWebHandler.SendAsyncCore (System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) [0x00092] in /_/src/Grpc.Net.Client.Web/GrpcWebHandler.cs:137
at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered (System.Threading.Tasks.Task`1[TResult] sendTask, System.Net.Http.HttpRequestMessage request, System.Threading.CancellationTokenSource cts, System.Boolean disposeCts) <0x3d9ae50 + 0x00134> in <filename unknown>:0
at Grpc.Net.Client.Internal.GrpcCall`2[TRequest,TResponse].RunCall (System.Net.Http.HttpRequestMessage request, System.Nullable`1[T] timeout) [0x0020c] in /_/src/Grpc.Net.Client/Internal/GrpcCall.cs:477 ")
at Google.Api.Gax.Grpc.ApiCallRetryExtensions+<>c__DisplayClass0_0`2[TRequest,TResponse].<WithRetry>b__0 (TRequest request, Google.Api.Gax.Grpc.CallSettings callSettings) [0x00051] in T:\src\github\gax-dotnet\releasebuild\Google.Api.Gax.Grpc\ApiCallRetryExtensions.cs:27
at Google.Cloud.Firestore.WriteBatch.CommitAsync (Google.Protobuf.ByteString transactionId, System.Threading.CancellationToken cancellationToken) [0x000b5] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/WriteBatch.cs:231
at Google.Cloud.Firestore.DocumentReference.SetAsync (System.Object documentData, Google.Cloud.Firestore.SetOptions options, System.Threading.CancellationToken cancellationToken) [0x0004b] in /_/apis/Google.Cloud.Firestore/Google.Cloud.Firestore/DocumentReference.cs:181
at BlazorFirestore.Pages.FetchData.OnInitializedAsync () [0x00166] in C:\Users\niels\source\repos\BlazorFirestore\Pages\FetchData.razor:21
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync () <0x2ff3be8 + 0x0013a> in <filename unknown>:0
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask (System.Threading.Tasks.Task taskToHandle) <0x3905a50 + 0x000b6> in <filename unknown>:0
When you explicitly disable CORS in your browser using .\chrome.exe --disable-web-security
, then the result will be a successful authentication but the actual API's will return 404.
Why it returns 404 is unclear to me. I can sniff the Grpc.Net.Client.Web
version with Fiddler, but the default implementation does not get intercepted by Fiddler.
It looks like the web-version uses a different protocol/transport that isn't supported or something. Hopefully somebody can resolve the next issue.
Conclusion: Firestore library for .NET isn't supposed to be used in the browser sandbox and isn't supported that way.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With