Set up external authentication

You can set up OpenID Connect (OIDC) authentication to secure access to your developer portal. To learn more about how external authentication works with your portal, see External authentication with the developer portal.

You can choose among the supported OIDC providers to secure your developer portal.

The example in this guide uses Keycloak as the OIDC provider.

Before you begin

  1. Make sure that you created your developer portal and exposed the portal on the gateway.

  2. Get the external address of the ingress gateway. If you deployed your ingress gateway in a different namespace or with a different version, update the command.

    export INGRESS_GW_IP=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    echo $INGRESS_GW_IP
    
    export INGRESS_GW_IP=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
    echo $INGRESS_GW_IP
    

    Note: Depending on your environment, you might see <pending> instead of an external IP address. For example, if you are testing locally in kind or minikube, or if you have insufficent permissions in your cloud platform, you can instead port-forward the service port of the ingress gateway:

    kubectl -n gloo-mesh-gateways port-forward deploy/istio-ingressgateway-1-18-2 8081
    
  3. For local testing with your browser, update your /etc/hosts DNS file. Map the IP address of your ingress gateway to resolve to the developer.example.com host domain that you set in the route table.

    sudo nano /etc/hosts
    

    Replace ${INGRESS_GW_IP} with the value that you previously retrieved.

    ${INGRESS_GW_IP}    developer.example.com
    

Set up authentication with Keycloak

  1. Follow the steps to install Keycloak.

  2. Create a Kubernetes secret with the Keycloak OIDC secret.

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Secret
    metadata:
      name: oauth
      namespace: gloo-mesh-addons
    type: extauth.solo.io/oauth
    data:
      client-secret: $(echo -n ${KEYCLOAK_SECRET} | base64)
    EOF
    
  3. Create an external auth server to use for your policy.

    kubectl apply -f - <<EOF
    apiVersion: admin.gloo.solo.io/v2
    kind: ExtAuthServer
    metadata:
      name: ext-auth-server
      namespace: gloo-mesh-addons
    spec:
      destinationServer:
        ref:
          cluster: $CLUSTER_NAME
          name: ext-auth-service
          namespace: gloo-mesh-addons
        port:
          name: grpc
    EOF
    
  4. Decide on the portal APIs that you want to enforce authentication for. In the following example, you want users to be authenticated before they can perform the me and api-keys actions. To apply an external auth policy to these APIs and enforce authentication, you add the oauth: "true" label on all of the routes.

    kubectl apply -f - <<EOF    
    apiVersion: networking.gloo.solo.io/v2
    kind: RouteTable
    metadata:
      name: dev-portal-rt
      namespace: gloo-mesh-gateways
    spec:
      hosts:
        - developer.example.com
      virtualGateways:
      - name: istio-ingressgateway-portal
        namespace: gloo-mesh-gateways
      defaultDestination:
        port:
          number: 8080
        ref:
          name: gloo-mesh-portal-server
          namespace: gloo-mesh-addons
      http:
      - forwardTo: {}
        labels: 
          oauth: "true"
        matchers:
        - uri: 
            prefix: /v1/api-keys
        - uri: 
            prefix: /v1/me
        - headers:
            - name: Cookie
              value: ".*?id_token=.*" 
              regex: true
      - forwardTo: {}
        labels: 
          oauth: "false"
        matchers:
        - uri: 
            prefix: /v1/apis
        - uri: 
            prefix: /v1/usage-plans
    EOF
    
    Setting Description
    spec.http.matchers.headers If a Cookie header is present in the client request, instruct the external auth server to retrieve the ID token from the header. Use .*?id_token=.* if you decided to store the raw ID token from your OIDC provider in the cookie. If you decided to store the ID token in Redis and to return a session ID instead, enter .*?session=.*.
  5. Create an external auth policy for the oauth: "true" routes to enforce authentication with the Keycloak OIDC provider that you set up. Make sure to replace $CLUSTER_NAME, $KEYCLOAK_CLIENT and $KEYCLOAK_URL with the values for your cluster name, Keycloak client and URL.

    kubectl apply -f -<<EOF
    apiVersion: security.policy.gloo.solo.io/v2
    kind: ExtAuthPolicy
    metadata:
      name: oidc-auth
      namespace: gloo-mesh-addons
    spec:
      applyToRoutes:
        - route:
            labels:
              oauth: "true"
      config:
        server:
          name: ext-auth-server
          namespace: gloo-mesh-addons
          cluster: $CLUSTER_NAME
        glooAuth:
           configs:
             - oauth2:
                 oidcAuthorizationCode:
                   appUrl: http://developer.example.com
                   callbackPath: /v1/me
                   clientId: $KEYCLOAK_CLIENT
                   clientSecretRef:
                     name: oauth
                     namespace: gloo-mesh-addons
                   issuerUrl: "${KEYCLOAK_URL}/realms/master/"
                   scopes:
                     - email
                   session:
                     failOnFetchFailure: true
                     cookie:
                       allowRefreshing: true
                     cookieOptions:
                       notSecure: true
                       maxAge: 3600
                   headers:
                     idTokenHeader: id_token
    EOF
    
    Setting Description
    spec.applyToRoutes Select the routes that you want to apply this policy to. In this example, you want to require external authentication for all routes with the oauth: "true" label.
    spec.config.server The external auth server to use for the policy.
    glooAuth.configs.oauth2.appUrl The URL users are redirected to to authenticate with Keycloak.
    glooAuth.configs.oauth2.callbackPath The callback path that users are getting redirected to after successful authentication. Gloo Gateway intercepts requests with this path to store the ID token that is returned from the OIDC provider. If you specified header matching of type ".*?id_token=.*" in the route table for your developer portal, the raw ID token is stored in the Set-Cookie response header. If you specified ".*?session=.*", the ID token is stored in Redis and a session ID is set in the Set-Cookie response header. Then, the request is forwarded to its original destination.
    The callback path must have a matching route in the route table that is associated with the external auth policy. In this example, /v1/me is defined in the route table for the developer portal.
    glooAuth.configs.oauth2.clientId The client ID token that you got when you registered the client in your OIDC provider.
    glooAuth.configs.oauth2.clientSecretRef The Kubernetes secret that has the client secret that you got when you registered the client in your OIDC provider. The secret must exist on the same cluster as the ExtAuthServer resource that this policy refers to.
    glooAuth.configs.oauth2.issuerUrl The URL of the OpenID Connect identity provider. Gloo Gateway automatically discovers OIDC configuration by querying the .well-known/openid-configuration endpoint on the issuer_url. In this example, Gloo Gateway expects to find OIDC discovery information at "${KEYCLOAK_URL}/realms/master/".
    glooAuth.configs.oauth2.scopes Scopes to request in addition to the openid scope, such as email in this example.
    glooAuth.configs.oauth2.idTokenHeader Forward the ID token to the destination after successful authentication. This setting is required indepent of the OIDC provider that you use.
  6. Verify that the external auth policy is applied successfully.

    1. Review the status of the external auth policy and make sure that it shows ACCEPTED.
      kubectl get extauthpolicy oidc-auth -n gloo-mesh-addons -o yaml
      
    2. Get the authconfig resource that was created for your policy and make sure that it shows ACCEPTED.
      kubectl get authconfig -n gloo-mesh-addons -o yaml
      

Verify external authentication

  1. Send a request to the portal server. Verify that you get back a 302 HTTP redirect response. The redirect URL is stored in the location field of your CLI output.

    curl -vik --resolve developer.example.com:80:$INGRESS_GW_IP http://developer.example.com:80/v1/me
    

    Example output:

       * Mark bundle as not supporting multiuse
       < HTTP/1.1 302 Found
       HTTP/1.1 302 Found
       < location: http://34.145.136.160:8080/auth/realms/master/protocol/openid-connect/auth?client_id=4464fbac-ab29-4c6a-945b-14e4685c0ad4&redirect_uri=http%3A%2F%2Fdeveloper.example.com%3A80%2Fv1%2Flogin&response_type=code&scope=email+openid&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODA5MTk5OTYsImlhdCI6MTY4MDkxODE5Niwic3RhdGUiOiJodHRwOi8vZGV2ZWxvcGVyLmV4YW1wbGUuY29tL3YxL2xvZ2luIn0.ubqut8s4LZCHqHGtXN4TO2Esnp48FRK5ARufmKiFDjo
       location: http://34.145.136.160:8080/auth/realms/master/protocol/openid-connect/auth?client_id=4464fbac-ab29-4c6a-945b-14e4685c0ad4&redirect_uri=http%3A%2F%2Fdeveloper.example.com%3A80%2Fv1%2Flogin&response_type=code&scope=email+openid&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODA5MTk5OTYsImlhdCI6MTY4MDkxODE5Niwic3RhdGUiOiJodHRwOi8vZGV2ZWxvcGVyLmV4YW1wbGUuY29tL3YxL2xvZ2luIn0.ubqut8s4LZCHqHGtXN4TO2Esnp48FRK5ARufmKiFDjo
       < date: Sat, 08 Apr 2023 01:43:16 GMT
       date: Sat, 08 Apr 2023 01:43:16 GMT
       < server: istio-envoy
       server: istio-envoy
       < content-length: 0
       content-length: 0
       

  2. In your preferred web browser, enter the URL that was returned in the location field of your CLI output to open the Keycloak login screen. Make sure to also open the developer utilities for your web browser. You use these utilities to retrieve the ID token that Keycloak returns after successful login. For example, if you use Google Chrome, you can right-click on the page and click Inspect. Then, go to the Network tab.

  3. Log in to Keycloak by using one of the usernames that you set up in the beginning. For example, you can enter user1@example.com as the email and password as the password.

  4. Make sure that after logging in, you are redirected to the developer.example.com/v1/me URL and that that user credentials are returned.

    Example output:

    {"email":"user1@example.com","name":"","username":"user1"}
    
  5. From the developer utilities Network tab, find the me request and find the Cookie header.

  6. Copy the id_token from the Cookie header and set it as an environment variable.

    export ID_TOKEN="id_token ey..."
    
  7. Send another request to the /v1/me API. This time, you get back a 200 HTTP response because you are successfully authenticated.

    curl -vik -H "Cookie: ${ID_TOKEN}" --resolve developer.example.com:80:$INGRESS_GW_IP http://developer.example.com:80/v1/me
    

    Example output:

    * Mark bundle as not supporting multiuse
    < HTTP/1.1 200 OK
    HTTP/1.1 200 OK
    < date: Mon, 10 Apr 2023 16:33:30 GMT
    date: Mon, 10 Apr 2023 16:33:30 GMT
    < content-length: 0
    content-length: 0
    < x-envoy-upstream-service-time: 0
    x-envoy-upstream-service-time: 0
    < server: istio-envoy
    server: istio-envoy
    
    {"email":"user1@example.com","name":"","username":"user1"}
    
  8. Continue with set up authorization for private APIs to create portal groups that let users access private APIs based on the claims in the ID token.

Troubleshoot issues

Use the following general steps if you encounter issues during your Keycloak setup.

  1. Make sure that the status of the external auth policy shows ACCEPTED.

    kubectl get extauthpolicy oidc-auth -n gloo-mesh-addons -o yaml
    
  2. Get the authconfig resource that was created for your policy and make sure that it shows ACCEPTED.

    kubectl get authconfig -n gloo-mesh-addons -o yaml
    
  3. If you used environment variables, such as $KEYCLOAK_CLIENT or $KEYCLOAK_URL, make sure that you entered the values of these environment variables in the external auth policy. If you used the variable names, the values might not be properly replaced.

  4. To get detailed logs for the external auth service, change the log level to DEBUG.

    1. Edit the external auth service.

      kubectl edit deploy -n gloo-mesh-addons ext-auth-service
      
    2. In the spec.container.env section, find the LOG_LEVEL environment variable and set it to DEBUG.

      ...
      spec:
        containers:
        - env:
          - name: LOG_LEVEL
            value: DEBUG
      
    3. Make sure that the external auth service pod restarts.

      kubectl get pods -n gloo-mesh-addons | grep ext-auth-service
      
  5. In a separate terminal, get the logs of the external auth service.

    kubectl logs <external-auth-service-pod> -n gloo-mesh-addons
    
  6. Send the curl request that is failing and review the logs that are returned by the external auth service.

Next steps

Next, set up authorization for private APIs.

When you are done with trying out Portal, you can clean up all of the resources that you created.