I'm using Blazor WebAssembly with gRPC and i'm new to Identity Server 4 and trying to implement google sign-in. I already followed the tutorial in the docs but when i tried to load the website, the console gave 2 errors like below. I searched many StackOverflow posts and GitHub issues similiar to this error and it didn't really helped me. My guess is that the error is in the server side because it happens when the website is loading.
Access to XMLHttpRequest at 'https://localhost:5000/signin-google/.well-known/openid-configuration' from origin 'https://localhost:5001' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
AuthenticationService.js:1 GET https://localhost:5000/signin-google/.well-known/openid-configuration net::ERR_FAILED
The error shows that it has been blocked by CORS policy even though i already allowed all website url to access it(for testing purposes) and when i'm trying to sign-in with google, i got redirected to a failed login url that says network error. Here's the code.
BackEnd/Startup.cs
namespace BackEnd
{
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.AddGrpc();
services.AddDbContext<UserDbContext>(options => options.UseInMemoryDatabase("UserDatabase"));
services.AddCors(o => o.AddPolicy("AllowAll", builder =>
{
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding");
}));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<UserDbContext>();
services.AddIdentityServer()
.AddInMemoryIdentityResources(ServerConfiguration.IdentityResources)
.AddInMemoryApiResources(ServerConfiguration.ApiResources)
.AddInMemoryApiScopes(ServerConfiguration.ApiScopes)
.AddInMemoryClients(ServerConfiguration.Clients)
// .AddApiAuthorization<ApplicationUser, UserDbContext>()
.AddTestUsers(ServerConfiguration.TestUsers);
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddTransient<IProfileService, ProfileService>();
services.AddAuthentication(options => {
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddGoogle("Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = <confidential>;
options.ClientSecret = <confidential>;
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://localhost:5000";
options.ClientId = "499675830263-ldcg4fm7kcbjlt48tpaffqdbfnskmi8v.apps.googleusercontent.com";
options.ResponseType = "code";
options.SaveTokens = true;
options.Scope.Add("protectedScope");
options.Scope.Add("offline_access");
options.Scope.Add("role");
options.ClaimActions.MapJsonKey("role", "role", "role");
options.TokenValidationParameters.RoleClaimType = "role";
});
services.AddAuthorization();
}
// 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.UseRouting();
app.UseCors();
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true });
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>().RequireCors("AllowAll");
endpoints.MapGrpcService<UserService>().RequireCors("AllowAll");
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");
});
});
}
}
}
BackEnd/ServerConfiguration.cs
namespace BackEnd
{
public static class ServerConfiguration
{
public static List<IdentityResource> IdentityResources {
get
{
List<IdentityResource> idResources = new List<IdentityResource>()
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
new IdentityResources.Email(),
new IdentityResource("roles", "User roles", new List<string> { "role" })
};
return idResources;
}
}
public static List<ApiScope> ApiScopes {
get
{
List<ApiScope> apiScopes = new List<ApiScope>();
apiScopes.Add(new ApiScope("protectedScope", "Protected Scope"));
return apiScopes;
}
}
public static List<ApiResource> ApiResources {
get
{
ApiResource userApiResource = new ApiResource("toDoWebApiResource", "Todo Web Api")
{
Scopes = { "protectedScope" },
UserClaims =
{
"openid",
"email",
"profile",
"role"
}
};
List<ApiResource> apiResources = new List<ApiResource>();
apiResources.Add(userApiResource);
return apiResources;
}
}
public static List<Client> Clients {
get
{
Client client = new Client()
{
ClientId = "499675830263-ldcg4fm7kcbjlt48tpaffqdbfnskmi8v.apps.googleusercontent.com",
ClientName = "client 1",
RequireClientSecret = false,
RequirePkce = true,
AllowedCorsOrigins = { "https://localhost:5001" },
AllowedGrantTypes = GrantTypes.Code,
RedirectUris = { "https://localhost:5001/authentication/login-callback" },
PostLogoutRedirectUris = { "https://localhost:5001/authentication/logout-callback" },
AllowOfflineAccess = true,
AllowedScopes = new List<string>{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"protectedScope"
}
};
List<Client> clients = new List<Client>();
clients.Add(client);
return clients;
}
}
public static List<TestUser> TestUsers {
get
{
TestUser user1 = new TestUser()
{
SubjectId = "2f47f8f0-bea1-4f0e-ade1-88533a0eaf57",
Username = "John",
Claims = new List<Claim>()
{
new Claim("role", "SignedInUser"),
new Claim("email", "[email protected]"),
new Claim("picture", "https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.business2community.com%2Fsocial-media%2Fimportance-profile-picture-career-01899604&psig=AOvVaw2LC5T-WZMYnHD9I7PeK7lT&ust=1615219065948000&source=images&cd=vfe&ved=2ahUKEwip1caGxp7vAhV1NbcAHd_2BFwQjRx6BAgAEAc")
}
};
List<TestUser> testUsers = new List<TestUser>();
testUsers.Add(user1);
return testUsers;
}
}
}
}
FrontEnd/wwwroot/appsettings.json
{
"Authentication":{
"Google": {
"Authority": "https://localhost:5000",
"ClientId": <confidential>,
"ClientSecret": <confidential>,
"DefaultScopes": [
"email",
"profile",
"openid"
],
"PostLogoutRedirectUri": "https://localhost:5001/authentication/logout-callback",
"RedirectUri": "https://localhost:5001/authentication/login-callback",
"ResponseType": "code"
}
}
}
I found an answer to this problem, what i need to do is quite simple. I create the gRPC server without Identity Server UI and configuration, i just need to add the UI for it to work.
Install IdentityServer4 UI template
dotnet new -i IdentityServer4.Templates
Add IdentityServer UI
dotnet new is4ui
Add the controller
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>().RequireCors("AllowAll");
endpoints.MapGrpcService<UserService>().RequireCors("AllowAll");
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");
});
// Add this
endpoints.MapDefaultControllerRoute().RequireAuthorization();
});