Skip to content
You are viewing the latest documentation for Solo Enterprise for kgateway. To access the documentation for older versions, use the version switcher.

For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.

Portal auth server

Page as Markdown

Deploy a dedicated portal auth server to isolate data-plane credential validation from admin traffic.

Note

This guide must be completed by a Portal admin.

About the portal auth server

When you deploy a portal, a single portal web server is deployed that handles two distinct workloads:

  • Portal admin traffic: Low-frequency CRUD operations for managing teams, apps, subscriptions, and credentials by using the portal frontend.
  • Portal data plane traffic: The GET /v1/metadata endpoint, which ExtAuth calls on every inbound API request to validate the caller’s API key or OAuth token. These are the credentials that portal users create through the portal frontend to access API products. This endpoint is called at high frequency and must respond with low latency.

Because both workloads share the same process, database connection pool, and fault domain, a burst of admin activity can consume resources that slow or interrupt the GET /v1/metadata endpoint and can lead to API request failures.

To prevent resource starvation or high latency on the GET /v1/metadata endpoint, you can deploy a separate portal auth server by adding the spec.authServer block to your PortalParameters resource. The auth server runs in restricted mode with the following settings:

  • Serves only /healthz, /readyz, and GET /v1/metadata endpoints. All other routes return 404.
  • Skips database schema migrations, API product informers, and the admin API.
  • Can be configured with independent replica counts, resource limits, and PostgreSQL credentials.

Note

The portal auth server handles only API key and OAuth token validation. The OIDC login flow for portal user authentication (/v1/login and /v1/logout paths) continues to run on the main portal web server.

Consider deploying a separate portal auth server when:

  • You need credential validation to remain available and responsive, even during load spikes in admin operations or portal web server restarts.
  • You want to scale the portal data plane component independently of the portal admin component.
  • You want credential validation queries to hit a read-only PostgreSQL replica instead of your primary database to reduce read operations.
  • Your environment has strict fault isolation requirements between control plane and data plane components.

Before you begin

  1. Complete the Get started with portal guide to install the portal controller and create a portal.
  2. Configure PostgreSQL as your portal database by following the Portal database guide. The portal auth server requires a persistent relational database. You cannot use the default in-memory storage that is built into the portal.
  3. Set up API key management by following the API keys guide. The verification step in this guide requires an AuthConfig with a portalAuth.url already in place to confirm that credential validation routes through the auth server.
  4. Create an API key in the portal frontend. You use this key to make a test request in the verification step.

Step 1: Deploy the portal auth server

Add an authServer block to your PortalParameters resource. The presence of this block deploys the Portal Auth Server.

  1. Update the PortalParameters resource to add the authServer block.

    kubectl apply -f- <<EOF
    apiVersion: portal.solo.io/v1alpha1
    kind: PortalParameters
    metadata:
      name: portal-params
      namespace: default
    spec:
      store:
        postgres:
          secretRef:
            name: portal-postgres-secret
      authServer:
        replicas: 1
        logLevel: debug
    EOF

    Note

    When the authServer.postgres field is not set, the portal auth server inherits the PostgreSQL connection details from the spec.store.postgres field.

    FieldDescription
    authServer.replicasNumber of portal auth server replicas.
    authServer.logLevelLog level for the portal auth server. Supported values include debug, info, warn, or error.
    authServer.resourcesCPU and memory resource requests and limits for the portal auth server container.
    authServer.postgres.secretRef.nameName of a Kubernetes secret with PostgreSQL connection details. Set this field to point the auth server at a separate PostgreSQL instance or read replica. If omitted, the auth server uses the same connection details as the portal admin web server. For example setup steps, see the Optional: Use a separate Postgres instance guide.
  2. Verify that the portal auth server deployment and service are created. The auth server is named portal-auth-<portal-name>.

    kubectl get deployment -n default | grep portal-auth
    kubectl get service -n default | grep portal-auth

    Example output:

    portal-auth-my-portal   2/2     2            2           3m3s
    portal-auth-my-portal   ClusterIP      10.96.53.219    <none>        8080/TCP         3m3s
    

Step 2: Update your AuthConfigs

Update the portalAuth.url field in the AuthConfig resources that you use to protect your API products to point to the portal auth server service (portal-auth-my-portal) instead of the combined portal web server (portal-my-portal). Select the tab that matches your credential type.

kubectl apply -f- <<EOF
apiVersion: extauth.solo.io/v1
kind: AuthConfig
metadata:
  name: httpbin-auth
  namespace: default
spec:
  configs:
    - name: httpbinAuth
      portalAuth:
        url: http://portal-auth-my-portal.default.svc.cluster.local:8080
        cacheDuration: 10s
        apiKeyHeader: "api-key"
EOF
kubectl apply -f- <<EOF
apiVersion: extauth.solo.io/v1
kind: AuthConfig
metadata:
  name: httpbin-auth
  namespace: default
spec:
  configs:
    - name: oauth2Validation
      oauth2:
        accessTokenValidation:
          jwt:
            remoteJwks:
              url: "$KEYCLOAK_URL/realms/portal/protocol/openid-connect/certs"
    - name: httpbinAuth
      portalAuth:
        url: http://portal-auth-my-portal.default.svc.cluster.local:8080
EOF

Step 3: Verify the portal auth server

Confirm that credential validation requests route through the portal auth server and not the portal web server. Select the tab for your credential type.

  1. Set your API key as an environment variable. Use the API key you created in the Before you begin section.

    export API_KEY="<your-api-key>"
  2. Send a request to your API product.

    curl -vik localhost:8080/httpbin/headers \
     -H "host: api.example.com" \
     -H "api-key: $API_KEY"
  3. Check the portal auth server logs for the /v1/metadata call. Note that it might take a few minutes for the logs to show.

    kubectl logs deployment/portal-auth-my-portal -n default --tail=50 | grep "v1/metadata"

    Example output:

    {"time":"2026-07-16T19:15:26.007809131Z","level":"debug","msg":"http request","component":"portal/webserver/access",
    "role":"portal-auth","method":"GET","path":"/v1/metadata","query":"apiKey=%3Credacted%3E&apiProductId=httpbin","status":200,
    "duration_ms":0}
    
  4. Confirm that the portal web server did not receive the /v1/metadata call. An empty response confirms that these requests are not routed through the portal web server.

    kubectl logs deployment/portal-my-portal -n default --tail=50 | grep "v1/metadata"
  1. Set the OAuth client credentials that you received from the portal as environment variables.

    export OAUTH_CLIENT="<your-oauth-client-id>"
    export OAUTH_SECRET="<your-oauth-client-secret>"
  2. Request an access token from your IdP using the client credentials.

    export ACCESS_TOKEN=$(curl -X POST -Ssm 10 --fail-with-body \
      -H "Content-Type: application/x-www-form-urlencoded" \
      -d "client_id=${OAUTH_CLIENT}" \
      -d "client_secret=${OAUTH_SECRET}" \
      -d "grant_type=client_credentials" \
      "$KEYCLOAK_URL/realms/portal/protocol/openid-connect/token" |
      jq -r .access_token)
    echo $ACCESS_TOKEN
  3. Send a request to your API product by using the access token.

    curl -vik localhost:8080/httpbin/headers \
    -H "host: api.example.com" \
    -H "Authorization: Bearer $ACCESS_TOKEN"
  4. Check the portal auth server logs for the /v1/metadata call.

    kubectl logs deployment/portal-auth-my-portal -n default --tail=50 | grep "v1/metadata"

    Example output:

    {"time":"2026-07-16T19:15:26.007809131Z","level":"debug","msg":"http request","component":"portal/webserver/access",
    "role":"portal-auth","method":"GET","path":"/v1/metadata","query":"accessToken=%3Credacted%3E&apiProductId=httpbin","status":200,
    "duration_ms":0}
    
  5. Confirm that the portal web server did not receive the /v1/metadata call. An empty response confirms that these requests are not routed through the portal web server.

    kubectl logs deployment/portal-my-portal -n default --tail=50 | grep "v1/metadata"

Optional: Use a separate Postgres instance

For stronger fault isolation, you can point the portal auth server at a separate PostgreSQL instance so that admin database activity does not affect credential validation latency.

  1. Create a secret with the connection details for the separate Postgres instance.

    kubectl apply -f- <<EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: portal-auth-postgres-secret
      namespace: default
    type: Opaque
    stringData:
      host: "<read-replica-host>"
      port: "5432"
      username: "<username>"
      password: "<password>"
      database: "<database>"
      sslmode: "disable"
    EOF
  2. Reference the secret in the authServer.postgres block.

    kubectl apply -f- <<EOF
    apiVersion: portal.solo.io/v1alpha1
    kind: PortalParameters
    metadata:
      name: portal-params
      namespace: default
    spec:
      store:
        postgres:
          secretRef:
            name: portal-postgres-secret
      authServer:
        replicas: 2
        postgres:
          secretRef:
            name: portal-auth-postgres-secret
    EOF

Note

The portal auth server does not run database schema migrations. Ensure that the separate PostgreSQL instance stays in sync with the primary database schema.

Was this page helpful?