DEX - attempt to use it as auth for services

TLDR: no, it is going to work only for full apps with authorization code flows, and won't work for cases when we need service to service communication

Usually when it comes to DEX it is mentioned as DYI solution for kubectl auth

But from what I see it is an almost complete OIDC implementation that may be used for auth between the services

From its getting started guide we gonna need:

git clone https://github.com/dexidp/dex.git
cd dex
make build
make examples

./bin/dex serve examples/config-dev.yaml
./bin/example-app

open http://localhost:5555

Use [email protected] and password for login button

The demo above starts DEX itself with sqlite storage and preconfigured client application

Dex, as OIDC does have discovery http://localhost:5556/dex/.well-known/openid-configuration with all expected endpoints, including jwks, which means we should be able to consume it in dotnet for example like so:

using System.Security.Claims;
using Microsoft.AspNetCore.Authentication.JwtBearer;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthorization().AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
  options.Authority = "http://127.0.0.1:5556/dex";
  options.Audience = "example-app"; // using same audience as in given samples, coz it is checked by default
  options.RequireHttpsMetadata = false; // just for local demo purposes
});

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapGet("/", (ClaimsPrincipal user) => user.Claims.Select(c => new KeyValuePair<string, string>(c.Type, c.Value)).ToList());

app.Run();

curl http://localhost:5000 -H 'Authorization: Bearer paste_token_from_prev_step_here'

our jwt looks like this

{
  "iss": "http://127.0.0.1:5556/dex",
  "sub": "CiQwOGE4N...",
  "aud": "example-app",
  "exp": 1692439988,
  "iat": 1692353588,
  "at_hash": "GvkUlwJgjtMVHU4q79kIeg",
  "email": "[email protected]",
  "email_verified": true,
  "name": "admin"
}

and response

[
  { "key": "iss", "value": "http://127.0.0.1:5556/dex" },
  { "key": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", "value": "CiQwOGE4Njg0Yi." },
  { "key": "aud", "value": "example-app" },
  { "key": "exp", "value": "1692439988" },
  { "key": "iat", "value": "1692353588" },
  { "key": "at_hash", "value": "GvkUlwJgjtMVHU4q79kIeg" },
  { "key": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "value": "[email protected]" },
  { "key": "email_verified", "value": "true" },
  { "key": "name", "value": "admin" }
]

Dex - static clients

Even so there is sqlite storage the easiest way will be to configure clients manually via configuration file, so here is our edited config from getting started guide

issuer: http://127.0.0.1:5556/dex
storage:
  type: sqlite3
  config:
    file: examples/dex.db
web:
  http: 0.0.0.0:5556
# # will touch it little bit later
# frontend:
#   issuer: dex
#   logoURL: theme/logo.png
#   dir: web/
#   theme: light
telemetry: # http://localhost:5558/metrics
  http: 0.0.0.0:5558
# # will touch it little bit later
# grpc:
#   addr: 127.0.0.1:5557
#   tlsCert: examples/grpc-client/server.crt
#   tlsKey: examples/grpc-client/server.key
#   tlsClientCA: examples/grpc-client/ca.crt

logger:
  level: "debug"
  format: "text"

oauth2:
  passwordConnector: local # required for password flow

staticClients:
- id: example-app
  redirectURIs:
  - 'http://127.0.0.1:5555/callback'
  name: 'Example App'
  secret: ZXhhbXBsZS1hcHAtc2VjcmV0
- id: dotconsumer
  name: 'dotnet example'
  secret: HelloWorldMacWasHere2023

enablePasswordDB: true
staticPasswords:
- email: "[email protected]"
  hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"
  username: "admin"
  userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
- email: "[email protected]"
  # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
  hash: "$2y$10$WXvGiM44DOAUVKSKJAaa1eeUZSF2vIPugPBBvGjD.j4Wm6P.UAQKK"
  username: "mac"
  userID: "1"

if everything done correct we should be able to login as [email protected] using password as password

also we have created our very first client and by default almost all flows are supported including password, so we should be able to:

curl -s -X POST http://127.0.0.1:5556/dex/token \
  --data-urlencode "grant_type=password" \
  --data-urlencode "client_id=dotconsumer" \
  --data-urlencode "client_secret=HelloWorldMacWasHere2023" \
  --data-urlencode "[email protected]" \
  --data-urlencode "password=password" \
  --data-urlencode "scope=openid"

and it did worked out

Note: make sure to uncomment passwordConnector under oauth2 section for this to work

So far so good, but there is no client credentials flow or something like this, the only closes possible thing here is urn:ietf:params:oauth:grant-type:token-exchange which is described here RFC 8693 OAuth 2.0 Token Exchange but it is not something we can use here, but still here is an example:

Step 1: retrieve id token somehow

id_token=$(curl -s -X POST http://127.0.0.1:5556/dex/token \
  --data-urlencode "grant_type=password" \
  --data-urlencode "client_id=dotconsumer" \
  --data-urlencode "client_secret=HelloWorldMacWasHere2023" \
  --data-urlencode "[email protected]" \
  --data-urlencode "password=password" \
  --data-urlencode "scope=openid" | jq -r ".id_token")

Step 2: exchange it

curl http://localhost:8080/api/token -d grant_type=urn:ietf:params:oauth:grant-type:token-exchange -d subject_token=$ID_TOKEN -d subject_token_type=urn:ietf:params:oauth:token-type:id_token -d client_id=5908895171 -d scope=email

curl -s -X POST http://127.0.0.1:5556/dex/token \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  --data-urlencode "subject_token=$id_token" \
  --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:id_token" \
  --data-urlencode "client_id=dotconsumer" \
  --data-urlencode "client_secret=HelloWorldMacWasHere2023" \
  --data-urlencode "scope=openid"

But it won't work, because of missing connector_id found in sources, the intent of this flow is similar to the way of how we did the trick with github actions and azure here - aka to exchange tokens between identity providers

Also the fun fact is that password flow is also missing from discovery endpoint

Reading closely some issues in dex github repo have found notes about it is not a supported way of using it and wont be, so in case of need it is recommended to use other tools or write own implementation