Skip to content
agentgateway has joined the Agentic AI FoundationLearn more

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

Page as Markdown

Set up authentik

Deploy and configure authentik as an OAuth identity provider for MCP authentication with agentgateway.

Secure your Model Context Protocol (MCP) servers with OAuth 2.0 authentication by using agentgateway and authentik as the identity provider.

About this guide

In this guide, you configure the agentgateway proxy to protect a static MCP server with MCP auth by using authentik as the authorization server. Because authentik does not fully implement the OAuth behaviors that the MCP authorization specification assumes, agentgateway includes a native Authentik provider that bridges the gaps. When you set provider: Authentik, agentgateway serves authorization server metadata from authentik’s OpenID Connect discovery document. Agentgateway also injects a Dynamic Client Registration (DCR) endpoint that authentik does not provide, and answers registration requests with the client that you pre-register.

Important

Setting clientId is required for open source authentik. Those builds do not implement Dynamic Client Registration (RFC 7591), so the pre-registered client in clientId is the only way for MCP clients to complete registration. If you omit it, registration requests fail. authentik 2026.8.0 adds a registration endpoint (authentik#8751), but only as an enterprise feature.

For more information about MCP auth, see the About MCP auth page.

Before you begin

  1. Set up an agentgateway proxy.
  2. Follow the steps to set up an MCP server with a fetch tool.
  3. Install the experimental channel Gateway API.
    kubectl apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.0/experimental-install.yaml

Install authentik

Install authentik in your cluster to act as the authorization server.

  1. Add the authentik Helm repository.

    helm repo add authentik https://charts.goauthentik.io
    helm repo update authentik
  2. Set the credentials that bootstrap the authentik admin account and API token. In a production environment, generate strong values and store them in a secret manager.

    export AUTHENTIK_SECRET_KEY=$(openssl rand -base64 36 | tr -d '\n')
    export AUTHENTIK_BOOTSTRAP_PASSWORD='Admin123!docs'
    export AUTHENTIK_BOOTSTRAP_TOKEN='docs-bootstrap-token-0123456789'
  3. Install authentik with the bundled PostgreSQL and Redis dependencies. The authentik.postgresql.password value must match postgresql.auth.password so that the authentik server can connect to its database.

    helm upgrade --install authentik authentik/authentik \
      --namespace authentik --create-namespace \
      --version 2026.5.6 \
      --timeout 15m \
      --set authentik.secret_key="${AUTHENTIK_SECRET_KEY}" \
      --set authentik.bootstrap_password="${AUTHENTIK_BOOTSTRAP_PASSWORD}" \
      --set authentik.bootstrap_token="${AUTHENTIK_BOOTSTRAP_TOKEN}" \
      --set authentik.bootstrap_email='admin@example.com' \
      --set authentik.error_reporting.enabled=false \
      --set authentik.postgresql.password='authentik-docs-pg' \
      --set postgresql.enabled=true \
      --set postgresql.auth.password='authentik-docs-pg' \
      --set postgresql.auth.postgresPassword='authentik-docs-pg' \
      --set redis.enabled=true \
      --set redis.auth.enabled=false
  4. Wait for the authentik server to become available. The server does not accept API requests until it finishes migrating its database, which can take several minutes on a first install.

    kubectl wait --for=condition=Available deployment/authentik-server \
      -n authentik --timeout=15m
  5. Verify that the authentik pods are running.

    kubectl get pods -n authentik

    Example output:

    NAME                                READY   STATUS    RESTARTS   AGE
    authentik-postgresql-0              1/1     Running   0          2m
    authentik-server-564544fd8d-4lzw8   1/1     Running   0          1m
    authentik-worker-7ccdd7cb6f-bf2qp   1/1     Running   0          1m

Create an OAuth provider and application in authentik

Create an OAuth2 provider and an application in authentik, and capture the client ID that agentgateway uses.

  1. Expose the authentik API so that you can administer it from outside the cluster. The Helm chart gives the authentik server a ClusterIP Service, which agentgateway uses from inside the cluster. This extra Service is only for the administrative API calls in the following steps.

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Service
    metadata:
      name: authentik-admin
      namespace: authentik
    spec:
      type: LoadBalancer
      selector:
        app.kubernetes.io/name: authentik
        app.kubernetes.io/instance: authentik
        app.kubernetes.io/component: server
      ports:
      - name: http
        port: 9000
        targetPort: 9000
    EOF

    Note

    The Service listens on port 9000 rather than 80 on purpose. A local kind cluster publishes each LoadBalancer Service on the matching port of your workstation, so a second Service on port 80 collides with the gateway’s own LoadBalancer and never receives an address.

    export AUTHENTIK_ADDRESS=$(kubectl get svc -n authentik authentik-admin \
      -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}"):9000
    
    echo "authentik address: $AUTHENTIK_ADDRESS"
  1. Look up the flow, signing key, and scope IDs that the provider requires.

    export AUTHENTIK_API=http://${AUTHENTIK_ADDRESS}/api/v3
    export AK_AUTH_HEADER="Authorization: Bearer ${AUTHENTIK_BOOTSTRAP_TOKEN}"
    
    export AK_FLOW=$(curl -s -H "${AK_AUTH_HEADER}" \
      "${AUTHENTIK_API}/flows/instances/?slug=default-provider-authorization-implicit-consent" | jq -r '.results[0].pk')
    export AK_INVALIDATION_FLOW=$(curl -s -H "${AK_AUTH_HEADER}" \
      "${AUTHENTIK_API}/flows/instances/?slug=default-invalidation-flow" | jq -r '.results[0].pk')
    export AK_SIGNING_KEY=$(curl -s -H "${AK_AUTH_HEADER}" \
      "${AUTHENTIK_API}/crypto/certificatekeypairs/?has_key=true" | jq -r '.results[0].pk')
    export AK_SCOPES=$(curl -s -H "${AK_AUTH_HEADER}" \
      "${AUTHENTIK_API}/propertymappings/provider/scope/" \
      | jq -c '[.results[] | select(.scope_name=="openid" or .scope_name=="profile" or .scope_name=="email") | .pk]')
  2. Create a public OAuth2 provider. MCP clients are public clients that use PKCE, because they cannot keep a client secret.

    export AUTHENTIK_CLIENT_ID=$(curl -s -X POST -H "${AK_AUTH_HEADER}" -H "Content-Type: application/json" \
      "${AUTHENTIK_API}/providers/oauth2/" -d "{
        \"name\": \"agentgateway-mcp\",
        \"authorization_flow\": \"${AK_FLOW}\",
        \"invalidation_flow\": \"${AK_INVALIDATION_FLOW}\",
        \"client_type\": \"public\",
        \"signing_key\": \"${AK_SIGNING_KEY}\",
        \"property_mappings\": ${AK_SCOPES},
        \"grant_types\": [\"authorization_code\", \"refresh_token\", \"client_credentials\"],
        \"redirect_uris\": [{\"matching_mode\": \"regex\", \"url\": \".*\"}],
        \"sub_mode\": \"user_username\",
        \"include_claims_in_id_token\": true
      }" | jq -r '.client_id')
    
    echo "Client ID: ${AUTHENTIK_CLIENT_ID}"

    If the client ID is empty, the provider was not created. Check that AUTHENTIK_ADDRESS still resolves and that AUTHENTIK_BOOTSTRAP_TOKEN matches the value you installed authentik with.

    The following table describes the settings that matter for MCP.

    SettingDescription
    client_typeMust be public. MCP clients cannot keep a client secret, so they authenticate with PKCE instead.
    property_mappingsThe scopes that the provider can issue. The profile scope is what puts the groups claim in the token, which the authorization rule reads.
    grant_typesauthentik rejects any grant that is not listed here with invalid_grant. MCP clients use authorization_code.
    sub_modeSets the sub claim to the username, which makes tokens easier to read while you test.

    Warning

    The .* redirect URI matcher accepts any callback URL, so that you can connect different MCP clients while you test. Do not use it outside a test cluster. An authorization server that accepts any redirect URI lets an attacker intercept authorization codes by sending a victim through a crafted callback. In production, list only the callback URLs of the MCP clients that you allow.

  3. Create an application that uses the provider. The application slug appears in the issuer URL.

    export AK_PROVIDER_PK=$(curl -s -H "${AK_AUTH_HEADER}" \
      "${AUTHENTIK_API}/providers/oauth2/?name=agentgateway-mcp" | jq -r '.results[0].pk')
    
    curl -s -X POST -H "${AK_AUTH_HEADER}" -H "Content-Type: application/json" \
      "${AUTHENTIK_API}/core/applications/" -d "{
        \"name\": \"agentgateway MCP\",
        \"slug\": \"agentgateway-mcp\",
        \"provider\": ${AK_PROVIDER_PK}
      }" | jq -r '.slug'
  1. Create a group for the users that can access the MCP server, and add your user to it. When a client requests the profile scope, authentik puts the names of the groups that the user belongs to in the groups claim of the token. The authorization rule that you configure later reads that claim.

    export AK_GROUP_PK=$(curl -s -X POST -H "${AK_AUTH_HEADER}" -H "Content-Type: application/json" \
      "${AUTHENTIK_API}/core/groups/" -d '{"name": "mcp-agent"}' | jq -r '.pk')
    
    export AK_USER_PK=$(curl -s -H "${AK_AUTH_HEADER}" \
      "${AUTHENTIK_API}/core/users/?username=akadmin" | jq -r '.results[0].pk')
    
    curl -s -X POST -H "${AK_AUTH_HEADER}" -H "Content-Type: application/json" \
      "${AUTHENTIK_API}/core/groups/${AK_GROUP_PK}/add_user/" -d "{\"pk\": ${AK_USER_PK}}"
    
    echo "Group: ${AK_GROUP_PK}"

    Repeat the add_user request for each user that you want to give access to the MCP server.

  1. Save the issuer URL. authentik issuers take the form https://<authentik-host>/application/o/<app-slug>/, including the trailing slash. Because the agentgateway control plane fetches the JWKS from inside the cluster, use the in-cluster address of the authentik Service.
    export AUTHENTIK_ISSUER="http://authentik-server.authentik.svc.cluster.local/application/o/agentgateway-mcp/"
    export AUTHENTIK_JWKS_PATH="/application/o/agentgateway-mcp/jwks/"

Configure MCP auth

With your MCP backend configured, create an AgentgatewayPolicy that enforces authentik authentication and authorization for the MCP backend.

  1. Create an AgentgatewayPolicy with the Authentik provider. The policy validates tokens that authentik issues and uses a Common Expression Language (CEL) rule to require the mcp-agent group.

    kubectl apply -f- <<EOF
    apiVersion: agentgateway.dev/v1alpha1
    kind: AgentgatewayPolicy
    metadata:
      name: mcp-authentik-authn
    spec:
      # Target the HTTPRoute to apply authentication at the route level
      targetRefs:
      - group: gateway.networking.k8s.io
        kind: HTTPRoute
        name: mcp
      traffic:
        jwtAuthentication:
          mode: Strict
          providers:
          - # The authentik issuer URL, including the trailing slash
            issuer: "${AUTHENTIK_ISSUER}"
            # authentik sets the 'aud' claim to the OAuth client ID
            audiences:
            - "${AUTHENTIK_CLIENT_ID}"
            jwks:
              remote:
                # Reference the in-cluster authentik Service to fetch public keys
                backendRef:
                  name: authentik-server
                  kind: Service
                  namespace: authentik
                  port: 80
                # authentik serves JWKS at {issuer}/jwks/, not /.well-known/jwks.json
                jwksPath: "${AUTHENTIK_JWKS_PATH}"
          mcp:
            # Use the native authentik provider
            provider: Authentik
            # Required: authentik does not support Dynamic Client Registration
            clientId: "${AUTHENTIK_CLIENT_ID}"
            resourceMetadata:
              resource: http://localhost:8080/mcp
              scopesSupported:
              - openid
              - profile
              bearerMethodsSupported:
              - header
        # Allow only tokens from members of the mcp-agent group
        authorization:
          action: Allow
          policy:
            matchExpressions:
            - '"mcp-agent" in jwt.groups'
    EOF

    Review the following table to understand this configuration. For more information, see the JwtAuthentication API docs.

    SettingDescription
    providers[].issuerThe authentik issuer URL, including the trailing slash. This must exactly match the iss claim in tokens that authentik issues.
    providers[].audiencesThe OAuth client ID. authentik sets the aud claim of its tokens to the client ID rather than to a separate API identifier, so this value must match clientId.
    providers[].jwks.remote.backendRefThe in-cluster authentik Service that the control plane fetches public keys from.
    providers[].jwks.remote.jwksPathThe path to authentik’s JWKS endpoint. authentik serves keys at {issuer}/jwks/.
    mcp.providerThe identity provider to adapt agentgateway’s OAuth behavior to. In this example, Authentik is used.
    mcp.clientIdThe pre-registered public client that agentgateway returns to MCP clients that attempt Dynamic Client Registration. Required for authentik.
    mcp.resourceMetadataMCP OAuth resource metadata for discovery. Includes the resource identifier, supported scopes, and bearer token methods.
    authorization.policy.matchExpressionsCEL rules that authorize the claims in the verified JWT. This example requires membership in the mcp-agent group that you created. Requests that present a valid token without that group are denied with a 403 HTTP response code.

    Note

    When the policy is first applied, the control plane might briefly log jwks keyset ... isn't available until it completes the first JWKS fetch. This condition resolves on its own.

  2. Verify that the policy was accepted.

    kubectl get AgentgatewayPolicy mcp-authentik-authn -o yaml

    In the status section, confirm that the Accepted and Attached conditions are True.

  3. Update the HTTPRoute that routes incoming traffic to the MCP server to include the OAuth discovery paths. This way, the agentgateway proxy can serve the resource and authorization server metadata during the MCP auth flow.

    kubectl apply -f - <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: mcp
    spec:
      parentRefs:
      - group: gateway.networking.k8s.io
        kind: Gateway
        name: agentgateway-proxy
        namespace: agentgateway-system
      rules:
      - filters:
        # Enable CORS for browser-based MCP clients
        - type: CORS
          cors:
            allowCredentials: true
            allowHeaders:
            - Origin
            - Authorization
            - Content-Type
            allowMethods:
            - "*"
            allowOrigins:
            - "*"
            exposeHeaders:
            - Origin
            - Mcp-Session-Id
            maxAge: 86400
        backendRefs:
        - group: agentgateway.dev
          kind: AgentgatewayBackend
          name: mcp-backend
        matches:
        # Main MCP endpoint to connect to the MCP server
        - path:
            type: PathPrefix
            value: /mcp
        # Path to access resource server metadata
        - path:
            type: PathPrefix
            value: /.well-known/oauth-protected-resource/mcp
        # Path to access authorization server metadata, including the
        # gateway-served client registration endpoint
        - path:
            type: PathPrefix
            value: /.well-known/oauth-authorization-server/mcp
    EOF

Verify MCP auth

  1. Get the address of the agentgateway proxy.

    export INGRESS_GW_ADDRESS=$(kubectl get svc -n agentgateway-system agentgateway-proxy \
      -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}")
    
    echo "Gateway address: $INGRESS_GW_ADDRESS"
  2. Send an unauthenticated request to the MCP endpoint. Verify that the request is rejected with a 401 HTTP response code and a WWW-Authenticate header that points MCP clients to the protected resource metadata.

    curl -i http://$INGRESS_GW_ADDRESS:80/mcp -X POST \
      -H "Content-Type: application/json" \
      -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}},"id":1}'

    Example output:

    HTTP/1.1 401 Unauthorized
    www-authenticate: Bearer resource_metadata="http://localhost:8080/.well-known/oauth-protected-resource/mcp"
  3. Verify that the gateway serves the protected resource metadata.

    curl -s http://$INGRESS_GW_ADDRESS:80/.well-known/oauth-protected-resource/mcp | jq

    Example output:

    {
      "resource": "http://localhost:8080/mcp",
      "authorization_servers": ["http://localhost:8080/mcp"],
      "mcp_protocol_version": "2025-06-18",
      "resource_type": "mcp-server",
      "bearer_methods_supported": ["header"],
      "scopes_supported": ["openid", "profile"]
    }
  4. Verify that the gateway serves authorization server metadata from authentik’s discovery document, and that it injected a registration_endpoint that points back at the gateway. authentik’s own discovery document does not include this field.

    curl -s http://$INGRESS_GW_ADDRESS:80/.well-known/oauth-authorization-server/mcp \
      | jq '{issuer, jwks_uri, authorization_endpoint, registration_endpoint}'

    Example output:

    {
      "issuer": "http://authentik-server.authentik.svc.cluster.local/application/o/agentgateway-mcp/",
      "jwks_uri": "http://authentik-server.authentik.svc.cluster.local/application/o/agentgateway-mcp/jwks/",
      "authorization_endpoint": "http://authentik-server.authentik.svc.cluster.local/application/o/authorize/",
      "registration_endpoint": "http://localhost:8080/.well-known/oauth-authorization-server/mcp/client-registration"
    }
  5. Verify that the gateway answers Dynamic Client Registration with your pre-registered client, instead of proxying the request to authentik.

    curl -s -X POST http://$INGRESS_GW_ADDRESS:80/.well-known/oauth-authorization-server/mcp/client-registration \
      -H "Content-Type: application/json" \
      -d '{"client_name":"test-mcp-client","redirect_uris":["http://localhost:9999/callback"]}' \
      | jq -r '.client_id'

    The returned client ID matches the AUTHENTIK_CLIENT_ID value that you configured in the policy.

Connect an MCP client

Point your MCP client at the gateway’s MCP endpoint, such as http://localhost:8080/mcp. The client discovers the authorization server through the gateway, registers against the pre-registered client, and redirects the user to authentik to log in and consent.

Important

The authorization and token endpoints that the gateway advertises come from authentik. In this guide, those endpoints use the in-cluster Service address, which a browser outside the cluster cannot reach. To complete an interactive sign-in, expose authentik at an address that both your MCP client and the gateway can resolve, and set AUTHENTIK_ISSUER to that address.

Group-based authorization

The policy that you created gates the MCP endpoint on the mcp-agent group, which authentik puts in the groups claim of the token when the client requests the profile scope. Authentication alone is not enough: any caller that authentik issues a token to for this client passes JWT validation, including service accounts that authorize themselves rather than a user. The authorization rule denies those tokens with a 403 HTTP response code.

Because MCP authentication runs at the route level, every claim in the verified token is also available to other route-level policies, such as rate limiting and transformations. For more information about the rules that you can write, see Authorization.

To authorize individual tools instead of the whole MCP endpoint, use an MCP authorization policy. For more information, see Tool access.

Clean up

You can remove the resources that you created in this guide.
kubectl delete AgentgatewayPolicy mcp-authentik-authn
helm uninstall authentik -n authentik
kubectl delete namespace authentik
Was this page helpful?
Agentgateway assistant

Ask me anything about agentgateway configuration, features, or usage.

Note: AI-generated content might contain errors; please verify and test all returned information.

Tip: one topic per conversation gives the best results. Use the + button in the chat header to start a new conversation.

Switching topics? Starting a new conversation improves accuracy.
↑↓ navigate select esc dismiss

What could be improved?

Your feedback helps us improve assistant answers and identify docs gaps we should fix.

Need more help? Join us on Discord: https://discord.gg/y9efgEmppm

Want to use your own agent? Add the Solo MCP server to query our docs directly. Get started here: https://search.solo.io/.