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. Example steps are provided for access token validation with Keycloak and Okta.

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-20 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
    

Step 1: Set up the OAuth for external auth

To enforce external authentication for Gloo Platform Portal, you must have an OIDC provider to use to get an OAuth access token. The following examples uses Keycloak or Okta. For other options, see the OIDC and OAuth guides.

Follow the Okta access token example to configure Okta and create the external auth policy.
  1. Follow the steps to install Keycloak.
  2. Follow the steps to configure Keycloak.
  3. Create the secret and ext auth server. Note that you can skip creating the httpbin sample app route table, as you create a route table for portal later.
  4. Configure OAuth with external auth policy for access token validation.

Step 2: Set up routing

Now that you secured your routes with OAuth, set up the routing details for the portal server's APIs.

  1. Decide on the portal APIs that you want to enforce authentication for. In the following example, you set up two forward actions for authn and no-auth that allow access to different API methods.
    • authn: To apply an external auth policy to these APIs and enforce authentication, you add the oauth: "true" label on this route. Users must authenticate before they can perform certain API methods, such as GET /v1/me, POST /v1/api-keys, and DELETE /v1/api-keys. The access token for authentication is extracted from the Authorization header
    • no-auth: Users do not have to authenticate before they can perform certain read-only API methods, such as GET /v1/apis, GET /v1/usage-plans, and GET /v1/api-keys.
    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
          cluster: $CLUSTER_NAME
      http:
        #
        # Portal server routes that require authentication.
        #
        - forwardTo: {}
          name: authn
          labels:
            oauth: "true" # Use this label to apply an OAuth external auth policy
            route: portal-api
          matchers:
            #
            # /v1/me
            - uri:
                prefix: /v1/me
              method: OPTIONS
            - uri:
                prefix: /v1/me
              method: GET
              headers:
                - name: Authorization
                  value: "Bearer.*"
                  regex: true
            #
            # /v1/apis
            - uri:
                prefix: /v1/apis
              method: GET
              headers:
                - name: Authorization
                  value: "Bearer.*"
                  regex: true
            #
            # /v1/usage-plans
            - uri:
                prefix: /v1/usage-plans
              method: GET
              headers:
                - name: Authorization
                  value: "Bearer.*"
                  regex: true
            #
            # /v1/api-keys
            - uri:
                prefix: /v1/api-keys
              method: GET
              headers:
                - name: Authorization
                  value: "Bearer.*"
                  regex: true
            - uri:
                prefix: /v1/api-keys
              method: POST
              headers:
                - name: Authorization
                  value: "Bearer.*"
                  regex: true
            - uri:
                prefix: /v1/api-keys
              method: DELETE
              headers:
                - name: Authorization
                  value: "Bearer.*"
                  regex: true
        #
        # Portal server routes that are public and do not require authentication.
        #
        - forwardTo: {}
          name: no-auth
          labels:
            route: portal-api
          matchers:
            - uri:
                prefix: /v1/apis
              method: GET
            - uri:
                prefix: /v1/usage-plans
              method: GET
            - uri:
                prefix: /v1/api-keys
              method: GET
            #
            # Allow OPTION requests without authentication.
            #
            - uri:
                prefix: /v1/api-keys
              method: OPTIONS
            - uri:
                prefix: /v1/usage-plans
              method: OPTIONS
            - uri:
                prefix: /v1/apis
              method: OPTIONS
    EOF
    
  2. For local development and testing, apply a CORS policy. This way, you can access the portal frontend that you set up later from your localhost.
    kubectl apply -f -<<EOF
    apiVersion: security.policy.gloo.solo.io/v2
    kind: CORSPolicy
    metadata:
      name: dev-portal-cors
      namespace: gloo-mesh-gateways
    spec:
      applyToRoutes:
        - route:
            labels:
              route: portal-api
      config:
        allowCredentials: true
        allowHeaders:
          - "Content-Type"
          - "api-key"
          - "id_token"
          - "Authorization"
        allowMethods:
          - GET
          - POST
          - DELETE
          - PUT
          - OPTIONS
        allowOrigins:
          - prefix: http://localhost
          - prefix: http://127.0.0.1
          - prefix: https://developer.example.com
    EOF
    

Step 3: Verify external authentication

Now that the OIDC provider is set up and your Gloo external auth policy is configured, you can verify that OAuth protects the portal.

  1. Send a request to the portal server. Verify that you get back a 200 from an API that does not require authentication, such as GET /v1/apis.

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

    Example output:

    HTTP/1.1 200 OK
    ...
    [{"apiProductDisplayName":"Catstronauts Course Tracks","apiProductId":"tracks","apiVersions":[{"apiId":"tracks-v1","apiVersion":"v1","contact":"support@example.com","customMetadata":{"compatibility":"None"},"description":"REST API for Catstronauts to retrieve data for tracks, authors and modules.","license":"License info, such as MIT","lifecycle":"Supported","termsOfService":"You must authenticate to use this API! And other Terms of Service.","title":"Catstronauts REST API","usagePlans":["bronze","gold","silver"]}]},{"apiProductDisplayName":"Pet Store","apiProductId":"petstore","apiVersions":[{"apiId":"petstore-v1","apiVersion":"v1","contact":"support@example.com","customMetadata":{"compatibility":"None"},"description":"Totally awesome API for all things pets!","license":"License info, such as MIT","lifecycle":"Supported","termsOfService":"You must authenticate to use this API! And other Terms of Service.","title":"Pet Store REST API","usagePlans":["bronze","gold","silver"]}]}]
    
  2. Send another request to the portal server. Verify that you get back a 401 from an API that requires authentication, such as GET /v1/api-keys.

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

    Example output:

    HTTP/1.1 401 Unauthorized
    ...
    {"message":"user is not authenticated"}
    

After you set up a frontend app for the portal, you can verify that you can log in through the OIDC provider.

Next steps

Check out these common next steps.

Having trouble getting OAuth to work? Try debugging OAuth issues.