hi i am new to identity server 4. recently i made simple small project using identity server 4. solution has 3 projects of 1.identityserver4 2.api resource 3.client razor. every thing fine in localhost. but when i try the same project in docker i got problem with identityserver 4. this is the repository of the test project here
Problem
when web app client is started it should redirect to identity server 4 login page which happens with local host. but here i ran into the problem.
NOTE: until now according to searching i reach there may be certificate problem OR networking and DNS issue and i tried some suggested approaches but it wont work or maybe i doing it wrong
1st i tried set the authority address to the container name with port like http://identityserver4:9001 (also for client address which is port 5001) which throws the errors as
SocketException: Connection refused
System.Net.Sockets.Socket+AwaitableSocketAsyncEventArgs.ThrowException(SocketError
error, CancellationToken cancellationToken)
HttpRequestException: Connection refused (identityserver4:9001)
System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(string host,
int port, HttpRequestMessage initialRequest, bool async,
CancellationToken cancellationToken)
IOException: IDX20804: Unable to retrieve document from:
'http://identityserver4:9001/.well-known/openid-configuration'.
Microsoft.IdentityModel.Protocols.HttpDocumentRetriever.GetDocumentAsync(string
address, CancellationToken cancel)
InvalidOperationException: IDX20803: Unable to obtain configuration
from: 'http://identityserver4:9001/.well-known/openid-configuration'.
Microsoft.IdentityModel.Protocols.ConfigurationManager.GetConfigurationAsync(CancellationToken
cancel)
In this case the error came from client app.
When i shutdown the identityserver4 service and try the client it throws the same error. so it seems totally it could not reach identity server.
i tried the defined bridge network in docker-compose as well and i got the same result
also i tried http request to http://identityserver4:9001/.well-known/openid-configuration from client (i removed the client authorization & authentication stuff for test this) which throws the Connection refused error in response.
2nd i tried set the authority address to http://identityserver4:80. in this case the client app redirect to identity server 4 identityserver4/connect/authorize but the browser throws
dial tcp: lookup identityserver4 on 127.0.0.11:53: no such host
in this case the client passed and redirect to identity server 4
http request to http://identityserver4:80/.well-known/openid-configuration from client (i removed the client authorization & authentication stuff for test this) gives the correct response.
i linked the github repository above but for ease i also added mu configuration as below.
i try some approaches but this is the BASE CODE from the first attempt.
docker-compose.override.yml
version: '3.4'
services:
identityserver4:
container_name: identityserver4
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "9001:80"
networks:
- mynet
clientapp:
container_name: clientapp
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "5001:80"
depends_on:
- identityserver4
networks:
- mynet
apiresource:
container_name: apiresource
environment:
- ASPNETCORE_ENVIRONMENT=Development
ports:
- "8001:80"
networks:
- mynet
networks:
mynet:
external: true
Identity Server
client configuration in Config.cs
new Client
{
ClientId = "razorClient",
ClientName = "RAZOR Client App",
AllowedGrantTypes= GrantTypes.Hybrid,
RequirePkce = false,
AllowRememberConsent = false,
RedirectUris = new List<string>()
{
"http://clientapp:80/signin-oidc"
},
PostLogoutRedirectUris = new List<string>()
{
"http://clientapp:80/signout-callback-oidc"
},
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
AllowedScopes = new List<string>()
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"MYAPI"
},
AllowAccessTokensViaBrowser = true,
}
program.cs
using IdentityServer;
var builder = WebApplication.CreateBuilder(args);
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
builder.Services.AddControllersWithViews();
builder.Services.AddIdentityServer(options =>
{
options.IssuerUri = "http://identityserver4:80";
})
.AddInMemoryClients(Config.Clients)
.AddInMemoryIdentityResources(Config.IdentityResources)
//.AddInMemoryApiResources(Config.ApiResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddTestUsers(Config.TestUsers)
.AddDeveloperSigningCredential();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseHttpsRedirection();
}
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
app.Run();
Client APP
program.cs
using ClientApp.Data;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Mvc.Authorization;
var builder = WebApplication.CreateBuilder(args);
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
builder.Services.AddHttpClient();
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.Authority = "http://identityserver4:80";
options.MetadataAddress = "http://identityserver4:80/.well-known/openid-configuration";
//options.BackchannelHttpHandler = new HttpClientHandler
//{
// ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
//};
options.RequireHttpsMetadata = false;
options.ClientId = "razorClient";
options.ClientSecret = "secret";
options.ResponseType = "code id_token";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("MYAPI");
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
});
builder.Services.AddMvcCore(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHttpsRedirection();
// 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.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
i got the solution
Description
the problem is the container communication. when running the project on localhost (not on docker) every thing is set to localhost:port address including authority address of is4. the app is authorized or redirect to login page simply. but in docker env the authority have to addressed based on docker inter-communication address ( local host is unknown here which leads to connection refused ) which is known by docker containers network but not machine localhost. so in that case the authority of is4 works but when it comes to redirect to login page ( identity server 4 UI ) based on authority address that address is unknown by local machine so it throws DNS error. so according to #Dimitris Maragkos answer i set the base addresses to local machine Ip address like
options.Authority= "http://192.168.8.202:9001"
including the client and api relevant required addresses.
Related
I have a service A and B. I am running up keycloak service in the same container with service A.
So, configs for service A are the following:
[app]
PageSize = 10
JwtSecret = 233
PrefixUrl = http://127.0.0.1:8000
[sso]
Host = http://127.0.0.1:8080
AdminLogin = some_admin
AdminPassword = some_password
Realm = master
ClientID = my_client
ClientSecret = XXX
I set up a middleware that validates JWT (gotten by keycloak) in service A and it successfully works out.
I run service B in another container with this config:
[app]
PageSize = 10
JwtSecret = 233
PrefixUrl = http://127.0.0.1:8002
[sso]
Host = http://172.18.0.1:8080
AdminLogin = some_admin
AdminPassword = some_password
Realm = master
ClientID = my_client
ClientSecret = XXX
You have noticed that I couldn't use 127.0.0.1 in B's config because it cannot dial this address (because keycloak is running with A in the same container).
I use the same JWT in the request header for service B. After that it goes to keycloak and gets:
{
"code": "ERROR_AUTH_CHECK_TOKEN_FAIL",
"context": {
"code": 401,
"message": "401 Unauthorized: invalid_token: Token verification failed",
"type": "unknown"
}
}
Am I right that keycloak detects the proxing somehow and the error is thrown because of that?
Should I use separate clients for keycloak for both services? And if it is so, how do I verify JWT gotten from service A in the B service?
I solved my problem. Ticket that I got from A had the issuer host http://127.0.0.1:8080 and B sent a header with the host http://172.18.0.1:8080 that failed token validation. Briefly, these hosts must be the same
in a WorkerService .Net I am trying to configure a MassTransit host with RabbitMq but I am getting this Error
Reference to type 'IBusControl' claims it is defined in 'MassTransit', but it could not be found
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context,services) =>
{
services.AddHostedService<Worker>();
services.AddAutoMapper(typeof(Program));
//MassTransit-RabbitMQ Configuration
services.AddMassTransit(config => {
config.UsingRabbitMq((ctx, cfg) => {
cfg.Host(context.Configuration.GetValue<string>("EventBusSettings:HostAddress"));
});
});
services.AddMassTransitHostedService();
})
.Build();
await host.RunAsync();
What am I missing?
You are probably referencing an assembly that is not current. The latest version of MassTransit no longer requires the AddMassTransitHostedService configuration method. More details are available in the documentation.
I've been following this post on Medium to learn how to create and run a dotnet core console app in a docker container, and post to a dotnet core API in another container.
When I run the two applications side-by-side (without docker, i.e. just debugging in vscode), everything works OK - the console app can post to the API. However, when I run the applications in containers using docker-compose up --build, I get an error when the application tries to post to the api:
Unhandled exception. System.AggregateException: One or more errors occurred. (The SSL connection could not be established, see inner exception.)
System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
System.IO.IOException: The handshake failed due to an unexpected packet format.
Searching for solutions to this error hasn't helped much, and I feel that the problem may simply be connectivity between the two containers, but I've had no luck trying to resolve it.
My docker-compose file is as follows:
version: '3.4'
services:
publisher_api:
image: my_publisher_api:latest
container_name: my_publisher_api_container
build:
context: ./publisher_api
dockerfile: Dockerfile
worker:
image: my_worker
container_name: my_worker_container
depends_on:
- "publisher_api"
build:
context: ./worker
dockerfile: Dockerfile
My console app code (or at least the relevant part) is:
public static async Task PostMessage(object postData)
{
var json = JsonConvert.SerializeObject(postData);
var content = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
using (var httpClientHandler = new HttpClientHandler())
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
using (var client = new HttpClient(httpClientHandler))
{
var result = await client.PostAsync("https://my_publisher_api_container:80/values", content);
string resultContent = await result.Content.ReadAsStringAsync();
Console.WriteLine($"Server returned {resultContent}");
}
}
}
I wont post any of the API code, as I dont think any of it should be relevant, but please let me know if you think it would help.
If anyone has any idea on what the cause of this error is or how to resolve it, I'd appreciate the help.
Edit
Thought it would be useful to include the versions being used:
dotnet core: 3.0.101
docker: 19.03.5, build 633a0ea838
Looks like I had mad a couple of fairly obvious mistakes, however they're not so obvious when you're completely new to Docker, like me.
The hostname to post to should be the name of the service, not the container.In my case, I had to change the console app to post to the name of the API service declared in the docker-compose file, publisher_api.
Use HTTP instead of HTTPS. When I debugged the API locally, it launches with HTTPS by default. I assumed I would use HTTPS when running the container in docker, but this doesn't seem to work by default. Changing to HTTP resolved the issue (although this ideally will be a short-term solution).
So just for completeness, here's my updated code. Only the URL that the console app posts to had to change:
public static async Task PostMessage(object postData)
{
var json = JsonConvert.SerializeObject(postData);
var content = new StringContent(json, UnicodeEncoding.UTF8, "application/json");
using (var httpClientHandler = new HttpClientHandler())
{
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
using (var client = new HttpClient(httpClientHandler))
{
var result = await client.PostAsync("http://publisher_api80/values", content);
string resultContent = await result.Content.ReadAsStringAsync();
Console.WriteLine($"Server returned {resultContent}");
}
}
}
Setup
Trying to run chrome headless as a container (Image: https://hub.docker.com/r/alpeware/chrome-headless-trunk) in my docker-compose and connecting to it from another container.
Problem
To actually connect to chrome inside the container, I first need to retrieve the webSocketDebuggerUrl, which is available at http://0.0.0.0:9222/json/version of the chrome-headless.
The Problem is: my request to this path always fails with
RequestError: Error: connect ECONNREFUSED 0.0.0.0:9222
and cant get the webSocketDebuggerUrl to connect to chrome.
Some more Info
Also if I visit http://0.0.0.0:9222/json/version in my browser myself, copy the url and hardcode it into my puppeteer.connect(), it ONLY works as expected, if I replace the address of '0.0.0.0' to my (linked) container-name (specified in docker-compose): http://chrome:9222/json/version
If I try to request the webSocketDebuggerUrl from /json/version while using container-name address (http://chrome:9222/json/version) i get the error
StatusCodeError: 500 - "Host header is specified and is not an IP address or localhost."
My Code (abstraction)
const rp = require('request-promise')
const puppeteer = require('puppeteer-core')
let url = await rp({uri:'http://0.0.0.0:9222/json/version', json: true }).then(res => res.webSocketDebuggerUrl)
let browser = await puppeteer.connect({ browserWSEndpoint: url })
Well, since the errorMessage from the 500 said "host is specified", ist just set that header to empty, and now I can successfully request the webSocketDebuggerUrl.
The solution feels a bit hacky, so if anyone has a suggestion on how to improve it I'd be happy:
const puppeteer = require('puppeteer-core')
const rp = require('request-promise')
let websocket = await rp({uri:'http://chrome:9222/json/version', json: true, headers: {'Host': ''} })
.then(res => res.webSocketDebuggerUrl.replace('ws://','ws://chrome:9222'))
let browser = await puppeteer.connect({ browserWSEndpoint: websocket })
Background
When running the website with single application instance (container) - SignalR is working perfectly.
When scaling out to more instances (>1), it throws errors and is just not working. I looked for an explanation on the internet and found that I need to configure my Signalr to work in cluster. I choose Redis as my backplane.
I worked through many tutorials on how to do it right and it's just not working for me.
Environment
I working asp.net core v2.1 hosted in Google cloud. The application is deployed as Docker container, using Kestrel + nginx. The docker container runs in a Kubernetes cluster behind a load balancer.
My Configuration
Startup.cs:
public class Startup
{
public Startup(IConfiguration configuration,
IHostingEnvironment hostingEnvironment)
{
Configuration = configuration;
HostingEnvironment = hostingEnvironment;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddSignalR().AddRedis(options =>
{
options.Configuration = new ConfigurationOptions
{
EndPoints =
{
{ ConfigurationManager.Configuration["settings:signalR:redis:host"], int.Parse(ConfigurationManager.Configuration["settings:signalR:redis:port"])}
},
KeepAlive = 180,
DefaultVersion = new Version(2, 8, 8),
Password = ConfigurationManager.Configuration["settings:signalR:redis:password"]
};
});
// ...
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/errors/general");
app.UseHsts();
}
// nginx forward
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});
app.UseSignalR(routes =>
{
routes.MapHub<StatisticsHub>("/hubs/myhub");
});
}
}
In order to verify that the connection to Redis server succeeded, I checking the output window of the Kastrel:
Same behaviour (connected) found also on servers (2 replicas, not on development enviroment).
In order to verify that the Signalr is "really" using the Redis (not just connecting), I used redis-cli to connect the Redis server and found that:
From this I can understand that there are some "talks" on Redis.
I removed the website LoadBalancer (GCP) and deployed it agian. Now with Sticky-Session: ClientIP. This loadbalancer is routing requests to the different containers.
The only place that I didnt changed is the nginx configuration. I'm wrong?
The result
SignalR not working on the browser. These errors are from the browser console:
scripts.min.js:1 WebSocket connection to 'wss://site.com/hubs/myhub?id=VNxKLazEKr9FKM4GPZRDhA' failed: Error during WebSocket handshake: Unexpected response code: 404
scripts.min.js:1 Error: Failed to start the transport 'WebSockets': undefined
Error: Connection disconnected with error 'Error: Server returned handshake error: Handshake was canceled.'.
scripts.min.js:1 Error: Connection disconnected with error 'Error: Server returned handshake error: Handshake was canceled.'.
files.min.js:1 Uncaught (in promise) Error: Error connecting to signalR. Error= Error
...
Question
What I missed? What to check?