- Go 38.8%
- JavaScript 30.1%
- CSS 16.9%
- HTML 5.3%
- Smarty 4.2%
- Other 4.7%
| charts/token-getter | ||
| static | ||
| tmp | ||
| .gitignore | ||
| .prettierrc | ||
| AGENTS.md | ||
| Dockerfile | ||
| Dockerfile.dev | ||
| go.mod | ||
| LICENSE | ||
| main.go | ||
| Makefile | ||
| README.md | ||
Token Getter
A minimal web application that allows users to obtain OIDC access tokens via direct access grant (password credentials flow) from Keycloak, with optional OTP support for 2FA.
This enables us to use more complex authentication flows (requiring Keycloak role membership for flow completion) alongside transparent authn/authz policies (Envoy Gateway SecurityPolicies) to lock API access behind verifiable JWTs that we can more easily access directly for integrating third party applications with those APIs.
You should probably not use this paradigm if you're not acutely aware of:
- The problems with direct grants
- The cases where direct grants of OIDC tokens are superior to alternatives (such as fixed tokens)
- How to configure custom flows for Keycloak and restrict clients to those flows
Disclosure
I built this program over the course of a few days while experimenting with OpenCode and a locally hosted Large Language Model, Qwen 3.5 35B A3B, running in llama.cpp on a Framework Desktop. I was trying to somewhat push the limits of local AI on this (very) powerful chip, with a state-of-the-art Mixture-of-Experts model that best takes advantage of its memory density:compute performance tradeoffs. Absolutely all generated code was reviewed by me personally, and a bunch of it was manually modified (much to OpenCode's dismay) when it wasn't able to produce output that I held up to par. I can't totally speak to the license of the code it generated, but I would feel comfortable arguing that I own enough of it for it to at least be transformative.
If you want to see the plan I started with (mostly true, this got updated a little bit as we went) before arguing with the clanker for a few days, AGENTS.md is available.
Part of that is to say that if you take issue with something here, take issue with me. The other part is to say the exact opposite. Which one I prefer depends on how much you pay your lawyers vs how much I pay mine. I think it's pretty good code at this point - I'm running it, after all.
Features
- Simple form-based interface for token retrieval
- One-click copy to clipboard with toast notification
- Expandable full response/error details
Building and Running Locally
Prerequisites
- Go 1.21 or later
- Podman or Docker (for containerized deployments)
- Environment variables to connect to your OIDC endpoint
Build and Run
go build -o token-getter .
. .env # or export them all yourself like a caveman
./token-getter
Environment Variables
| Variable | Description | Default |
|---|---|---|
| PORT | Server port | 8080 |
| OIDC_ENDPOINT | Keycloak OIDC endpoint | Required |
| OIDC_REALM | Keycloak realm name | sso |
| OIDC_CLIENT_ID | Client ID | Required |
| OIDC_CLIENT_SECRET | Client secret | Required |
| OIDC_SCOPE | OIDC scope | openid profile email |
| OIDC_TIMEOUT | Timeout for OIDC requests | 30s |
| CORS_ALLOWED_ORIGINS | Comma-separated allowed CORS origins | Same-origin only |
| TRUST_PROXY_HEADERS | Trust X-Forwarded-For headers | false |
| RATE_LIMIT_REQUESTS | Max requests per window per IP | 10 |
| RATE_LIMIT_WINDOW | Rate limiting window duration | 1m |
| READ_TIMEOUT | HTTP server read timeout | 15s |
| WRITE_TIMEOUT | HTTP server write timeout | 15s |
| IDLE_TIMEOUT | HTTP server idle timeout | 60s |
| MAX_HEADER_BYTES | Maximum HTTP header bytes | 1048576 (1MB) |
Most of the Makefile targets respect an .env file:
OIDC_ENDPOINT=https://keycloak.example.com
OIDC_REALM=your-realm
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
Container Development
# Build and run dev container with hot reload
make dev
Production Container
# Build with git tag as container image tag, and also tag latest
make build
# Push to registry
make push
If you just want to quickly run the built image from my registry, there's a target for that.
# Run locally
make run
The web UI and API should be listening on localhost:8080 after that.
Kubernetes Deployment
Helm Chart
The project includes a Helm chart in charts/token-getter/.
# Install or upgrade
helm upgrade --install token-getter ./charts/token-getter
--set route.enabled=true \
--set route.hostname=token.example.com \
--set-json route.parentRefs='[
{
"group": "gateway.networking.k8s.io",
"kind": "Gateway",
"name": "my-gateway",
"namespace": "my-gateway-namespace"
}
]'
See values.yaml for more options and details.
Keycloak Configuration
Enable Direct Access Grants
- Navigate to your Keycloak realm
- Go to Clients and select your client
- Enable "Direct access grants" under "Access settings"
- Save changes
For more information, see the Keycloak documentation.
While this grant type is not recommended to be used, we are using it here for cases where we need an access token to get past JWT inspection for authn/authz protection on a particular endpoint, without requiring that that endpoint be aware of the nature of that authn/authz protection.
Token Request Example
If you're not sure if your Keycloak server is configured correctly to provide access tokens for direct grants, the following command will provide JSON output that's suitable to provide an access token directly from Keycloak that you can test with a curl command to your API endpoint directly as a Bearer Token:
curl -X POST "https://keycloak.example.com/realms/your-realm/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=your-client-id" \
-d "client_secret=your-client-secret" \
-d "username=user@example.com" \
-d "password=mypassword" \
-d "otp=123456" # Optional for 2FA