Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use IdentityServer4 from inside and outside a docker machine?

I want to be able to authenticate against an Identity Server (STS) from outside and inside a docker machine.

I am having trouble with setting the correct authority that works both inside and outside the container. If I set the authority to the internal name mcoidentityserver:5000 then the API can authenticate but the client cannot get a token as the client lies outside of the docker network. If I set the authority to the external name localhost:5000 then the client can get a token but the API doesn't recognise the authority name (because localhost in this case is host machine).

What should I set the Authority to? Or perhaps I need to adjust the docker networking?

Diagram

The red arrow is the part that I'm having trouble with. Three docker containers in a network, a client and PostgreSQL Admin, their ports and a red arrow showing where I think the problem lies.

Detail

I am setting up a Windows 10 docker development environment that uses an ASP.NET Core API (on Linux), Identity Server 4 (ASP.NET Core on Linux) and a PostgreSQL database. PostgreSQL isn't a problem, included in the diagram for completeness. It's mapped to 9876 because I also have a PostgreSQL instance running on the host for now. mco is a shortened name of our company.

I have been following the Identity Server 4 instructions to get up and running.

Code

I'm not including the docker-compose.debug.yml because it has run commands pertinent only to running in Visual Studio.

docker-compose.yml

version: '2'  services: mcodatabase:     image: mcodatabase     build:     context: ./Data     dockerfile: Dockerfile     restart: always     ports:     - 9876:5432     environment:     POSTGRES_USER: mcodevuser     POSTGRES_PASSWORD: password     POSTGRES_DB: mcodev     volumes:     - postgresdata:/var/lib/postgresql/data     networks:     - mconetwork  mcoidentityserver:     image: mcoidentityserver     build:     context: ./Mco.IdentityServer     dockerfile: Dockerfile     ports:     - 5000:5000     networks:     - mconetwork  mcoapi:     image: mcoapi     build:     context: ./Mco.Api     dockerfile: Dockerfile     ports:     - 56107:80     links:     - mcodatabase     depends_on:     - "mcodatabase"     - "mcoidentityserver"     networks:     - mconetwork  volumes: postgresdata:  networks: mconetwork:     driver: bridge 

docker-compose.override.yml

This is created by the Visual Studio plugin to inject extra values.

version: '2'  services: mcoapi:     environment:     - ASPNETCORE_ENVIRONMENT=Development     ports:     - "80"   mcoidentityserver:     environment:     - ASPNETCORE_ENVIRONMENT=Development     ports:     - "5000"  

API Dockerfile

FROM microsoft/aspnetcore:1.1 ARG source WORKDIR /app EXPOSE 80 COPY ${source:-obj/Docker/publish} . ENTRYPOINT ["dotnet", "Mco.Api.dll"] 

Identity Server Dockerfile

FROM microsoft/aspnetcore:1.1 ARG source WORKDIR /app COPY ${source:-obj/Docker/publish} . EXPOSE 5000 ENV ASPNETCORE_URLS http://*:5000 ENTRYPOINT ["dotnet", "Mco.IdentityServer.dll"] 

API Startup.cs

Where we tell the API to use the Identity Server and set the Authority.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) {     loggerFactory.AddConsole(Configuration.GetSection("Logging"));     loggerFactory.AddDebug();      app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions     {         // This can't work because we're running in docker and it doesn't understand what localhost:5000 is!         Authority = "http://localhost:5000",          RequireHttpsMetadata = false,          ApiName = "api1"     });      if (env.IsDevelopment())     {         app.UseDeveloperExceptionPage();     }     else     {         app.UseExceptionHandler("/Home/Error");     }      app.UseMvc(routes =>     {         routes.MapRoute(             name: "default",             template: "{controller=Home}/{action=Index}/{id?}");     }); } 

Identity Server Startup.cs

public class Startup {     public void ConfigureServices(IServiceCollection services)     {         services.AddIdentityServer()             .AddTemporarySigningCredential()             .AddInMemoryApiResources(Config.GetApiResources())             .AddInMemoryClients(Config.GetClients());     }      public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)     {         loggerFactory.AddConsole();          if (env.IsDevelopment())         {             app.UseDeveloperExceptionPage();         }          app.UseIdentityServer();          app.Run(async (context) =>         {             await context.Response.WriteAsync("Hello World!");         });     } } 

Identity Server Config.cs

public class Config {     public static IEnumerable<ApiResource> GetApiResources()     {         return new List<ApiResource>         {             new ApiResource("api1", "My API")         };     }      public static IEnumerable<Client> GetClients()     {         return new List<Client>         {             new Client             {                 ClientId = "client",                  // no interactive user, use the clientid/secret for authentication                 AllowedGrantTypes = GrantTypes.ClientCredentials,                  // secret for authentication                 ClientSecrets =                 {                     new Secret("secret".Sha256())                 },                  // scopes that client has access to                 AllowedScopes = { "api1" }             }         };     } } 

Client

Running in a console app.

var discovery = DiscoveryClient.GetAsync("localhost:5000").Result; var tokenClient = new TokenClient(discovery.TokenEndpoint, "client", "secret"); var tokenResponse = tokenClient.RequestClientCredentialsAsync("api1").Result;  if (tokenResponse.IsError) {     Console.WriteLine(tokenResponse.Error);     return 1; }  var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken);  var response = client.GetAsync("http://localhost:56107/test").Result; if (!response.IsSuccessStatusCode) {     Console.WriteLine(response.StatusCode); } else {     var content = response.Content.ReadAsStringAsync().Result;     Console.WriteLine(JArray.Parse(content)); } 

Thanks in advance.

like image 921
Dr Rob Lang Avatar asked May 11 '17 09:05

Dr Rob Lang


People also ask

Is IdentityServer4 obsolete?

IdentityServer4 support will last until the end of life of . NET Core 3.1 that means till November 2022. In that way, Duende provides new documentation for the fifth service version.

What is the use of IdentityServer4?

IdentityServer is an authentication server that implements OpenID Connect (OIDC) and OAuth 2.0 standards for ASP.NET Core. It's designed to provide a common way to authenticate requests to all of your applications, whether they're web, native, mobile, or API endpoints.


1 Answers

Ensure IssuerUri is set to an explicit constant. We had similar issues with accessing Identity Server instance by the IP/hostname and resolved it this way:

services.AddIdentityServer(x => {     x.IssuerUri = "my_auth"; }) 

P.S. Why don't you unify the authority URL to hostname:5000? Yes, it is possible for Client and API both call the same URL hostname:5000 if:

  • 5000 port is exposed (I see it's OK)
  • DNS is resolved inside the docker container.
  • You have access to hostname:5000 (check firewalls, network topology, etc.)

DNS is the most tricky part. If you have any trouble with it I recommend you try reaching Identity Server by its exposed IP instead of resolving hostname.

like image 191
Ilya Chumakov Avatar answered Sep 23 '22 06:09

Ilya Chumakov