Securing Your APIs with Gloo Edge and Auth0

Gloo Edge is a cloud-native API Gateway and Ingress Controller built on Envoy Proxy to facilitate and secure application traffic at the edge. Enterprise users of Gloo Edge commonly want to delegate authentication decisions to an Identity Management Provider based on the popular OpenID Connect (OIDC) standard.
Auth0 is one of the most popular OIDC providers among the Gloo Edge user base. It can be used to expose a consistent OIDC interface to your applications while allowing your users to authenticate using credentials managed by Auth0.
This blog post will show you how to use Gloo Edge to authenticate users with your application via an OIDC flow that uses Auth0 as the identity provider. This guide is an example to get you started for test purposes with Auth0. It omits many of the factors that need to be considered for full production deployments.
First, we will use Gloo Edge to expose a simple httpbin service running on Kubernetes.
Second, we’ll secure access to the service using Auth0 OIDC. Auth0 will return a JWT token, and we’ll use Gloo Edge to extract some claims from this token create new headers corresponding to these claims.
Finally, we’ll see how Gloo Edge RBAC rules can be created to leverage the claims contained in the JWT token.
Prerequisites
You’ll need a Kubernetes cluster and associated tools, plus an instance of Gloo Edge Enterprise to complete this guide. Note that there is a free and open source version of Gloo Edge, but it does not contain the enhanced external auth integration used here.
We used GKE with Kubernetes v1.18.12 to test this guide, although any recent version with any Kubernetes provider should suffice. Use this guide if you need to install Gloo Edge Enterprise. If you choose to work locally, we’ve have good success with Kind. If you go the Kind route, use these instructions for your installation of Gloo Edge.
If you don’t already have access to the Enterprise bits of Gloo Edge, you can request a free trial here.
Expose a Kubernetes Service
Deploy the Service
httpbin
service on a Kubernetes cluster.kubectl apply -f https://raw.githubusercontent.com/istio/istio/master/samples/httpbin/httpbin.yaml
Verify the Upstream
Gloo Edge discovers Kubernetes services automatically and creates a routable target called an Upstream
. So, running the glooctl get upstreams
command, you should be able to see a new Gloo Edge Upstream default-httpbin-8000
, based on the naming convention namespace-serviceName-portNumber
:
% glooctl get upstreams default-httpbin-8000 +----------------------+------------+----------+------------------------+ | UPSTREAM | TYPE | STATUS | DETAILS | +----------------------+------------+----------+------------------------+ | default-httpbin-8000 | Kubernetes | Accepted | svc name: httpbin | | | | | svc namespace: default | | | | | port: 8000 | | | | | | +----------------------+------------+----------+------------------------+
Create the Virtual Service
kubectl
to create the following Gloo Edge Virtual Service that will route all requests from domain glootest.com
to the new Upstream.apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin-auth0-vs namespace: gloo-system spec: virtualHost: domains: - 'glootest.com' routes: - matchers: - prefix: / routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system
glooctl
command to confirm that the new Route was accepted by Gloo Edge.% glooctl get virtualservice httpbin-auth0-vs +------------------+--------------+--------------+------+----------+-----------------+----------------------------------+ | VIRTUAL SERVICE | DISPLAY NAME | DOMAINS | SSL | STATUS | LISTENERPLUGINS | ROUTES | +------------------+--------------+--------------+------+----------+-----------------+----------------------------------+ | httpbin-auth0-vs | | glootest.com | none | Accepted | | / -> | | | | | | | | gloo-system.default-httpbin-8000 | | | | | | | | (upstream) | +------------------+--------------+--------------+------+----------+-----------------+----------------------------------+
Configure DNS and Test
/etc/hosts
file to resolve glootest.com
by the IP address returned by the glooctl proxy address
command (without the port number).% glooctl proxy address 34.75.13.137 % tail -1 /etc/hosts 34.75.13.137 glootest.com
glootest.com
domain.% curl http://glootest.com/get { "args": {}, "headers": { "Accept": "*/*", "Content-Length": "0", "Host": "glootest.com", "User-Agent": "curl/7.64.1", "X-Envoy-Expected-Rq-Timeout-Ms": "15000" }, "origin": "10.68.1.7", "url": "http://glootest.com/get" }
httpbin
service sends back information mirroring the request that we issued.Secure the application using HTTPS
glootest.com
domain.% openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=glootest.com" Generating a 2048 bit RSA private key ...+++ ....+++ writing new private key to 'tls.key'
% kubectl create secret tls upstream-tls --key tls.key --cert tls.crt --namespace gloo-system secret/upstream-tls created
kubectl
to apply the following change. Note the new sslConfig
stanza in the yaml.apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin-auth0-vs namespace: gloo-system spec: sslConfig: secretRef: name: upstream-tls namespace: gloo-system virtualHost: domains: - 'glootest.com' routes: - matchers: - prefix: / routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system
VirtualService
change, we will use curl
to confirm that we can access the new https endpoint.% curl -k https://glootest.com/get { "args": {}, "headers": { "Accept": "*/*", "Content-Length": "0", "Host": "glootest.com", "User-Agent": "curl/7.64.1", "X-Envoy-Expected-Rq-Timeout-Ms": "15000" }, "origin": "10.68.1.7", "url": "https://glootest.com/get" }
Authenticate with Auth0 OIDC
Establish Auth0 Account
https://manage.auth0.com/dashboard/us/solo-io/
, where solo-io
would be replaced by the Auth0 Tenant Domain name that you choose. This provides access to an account dashboard and tools to manage Auth0 applications and users.Establish Auth0 Users
https://manage.auth0.com/dashboard/us/solo-io/users
. As before, you will need to replace solo-io
with your own Auth0 Tenant Domain.Establish Auth0 Application
Regular Web Application
type application and gave the application a name GlooTest
.Apache
, although that choice had no noticeable impact on our Gloo Edge setup.Allowed Callback URLs
, for which we provided a single value https://glootest.com/callback
. For more details on creating Regular Web Application
integrations with Auth0, we found this guide helpful.Our completed Auth0 GlooTest
application profile looks like this.
Users
tab that at least some of your Auth0 users have been assigned to your new application. Note that we have created a single user with a google-oauth2
connection.Establish Gloo Edge AuthConfig for Auth0 App
oauth
secret in Kubernetes using glooctl
with the Auth0 application secret.% glooctl create secret oauth --namespace gloo-system --name auth0-client-secret --client-secret $MY_CLIENT_SECRET +---------------------+-------+ | SECRET | TYPE | +---------------------+-------+ | auth0-client-secret | OAuth | +---------------------+-------+
AuthConfig
object to finish connecting the gateway to the OIDC provider. Be sure to customize the clientId
and issuerUrl
settings to fit your Auth0 configuration. Use kubectl
to apply this change.apiVersion: enterprise.gloo.solo.io/v1 kind: AuthConfig metadata: name: auth0-ac namespace: gloo-system spec: configs: - oauth2: oidcAuthorizationCode: appUrl: https://glootest.com callbackPath: /callback clientId: insert-your-client-id-here clientSecretRef: name: auth0-client-secret namespace: gloo-system issuerUrl: https://solo-io.us.auth0.com/ scopes: - email
scopes
parameter to indicate that the identity provider should include the email
of the user in the claims of the JWT token it will return.kubectl
to update the Virtual Service to use Auth0 OIDC authentication. Note the new extauth
stanza that references our freshly created AuthConfig
.apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin-auth0-vs namespace: gloo-system spec: sslConfig: secretRef: name: upstream-tls namespace: gloo-system virtualHost: domains: - 'glootest.com' routes: - matchers: - prefix: / routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system options: extauth: configRef: name: auth0-ac namespace: gloo-system
/callback
path will be handled by this same Virtual Service because we used a catch-all /
prefix matcher.Verify Auth0 Integration
Troubleshooting
section immediately following.https://glootest.com
. You should be redirected to the Auth0 Login page.Continue with Google
option to login as our google-oauth2
user./callback
endpoint we configured in the AuthConfig
, with the information it received from Auth0 OIDC added as a query string to create a Cookie. This cookie contains both an access_token
and an id_token
from Auth0. The id_token
is a JWT from which we will extract claims to drive fine-grained RBAC decisions later in this exercise.
httpbin
application responds as expected. You should get output that looks something like below. In particular, note the Cookie
header supplied by Auth0 containing both an access_token
and an id_token
.httpbin
endpoints via the Gloo Edge gateway. For example, consider this base64 conversion service endpoint: https://glootest.com/base64/R2xvbyBpcyBhd2Vzb21lCg==
Troubleshooting Auth0 Integration
403 Forbidden
errors, possibly with an ERR_CERT_INVALID
error code. With Safari, you can work through this by clicking through advanced settings and indicating you understand the risks. With Chrome, you may need to use the thisisunsafe
workaround described here.requestTimeout
parameter in the Gloo Settings
object. The default timeout is 200ms, which is often inadequate to account for the external network hop to Auth0. Increasing that timeout should resolve the problem.Settings
object like this:% kubectl get settings.gloo.solo.io -n gloo-system -oyaml
spec.extauth
stanza to add a requestTimeout
greater than 200ms, like this:extauth: requestTimeout: 1s extauthzServerRef: name: extauth namespace: gloo-system
JWT Claim Extraction
id_token
contains a JWT from which we can extract claims that may be useful in driving downstream RBAC policies. In this section, we will enhance our Virtual Service by applying a transformation.Transform Auth0 Cookie to JWT Header
stagedTransformations
stanza.apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin-auth0-vs namespace: gloo-system spec: sslConfig: secretRef: name: upstream-tls namespace: gloo-system virtualHost: domains: - 'glootest.com' routes: - matchers: - prefix: / routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system options: extauth: configRef: name: auth0-ac namespace: gloo-system stagedTransformations: early: requestTransforms: - requestTransformation: transformationTemplate: extractors: token: header: 'cookie' regex: '.*id_token=([^;]*).*' subgroup: 1 headers: jwt: text: '{{ token }}' headerManipulation: requestHeadersToRemove: - "cookie"
Cookie
header has been replaced by a Jwt
header.
Jwt
header into the JWT decoder at jwt.io, we can see some of the JWT claims that are available for us to make routing and authorization decisions. In particular, note the email
claim that we specified in the AuthConfig
resource.Convert JWT to Request Header
email
header that can drive fine-grained authorization decisions. Auth0 publishes a read-only JWKS endpoint where public keys can be extracted that allow us to decode our JWT and then place its claims into request headers. Details about how Auth0 publishes its JWKS keys is available here.Upstream
that exposes the JWKS endpoint that Auth0 publishes for our development account. Use kubectl
to apply this Upstream
to our cluster.apiVersion: gloo.solo.io/v1 kind: Upstream metadata: name: auth0-jwks-upstream namespace: gloo-system spec: static: hosts: # This upstream identifies the host where Auth0 publishes the JWKS endpoint for my dev account # See https://solo-io.us.auth0.com/ - addr: solo-io.us.auth0.com port: 443
email
claim to a custom header x-solo-claim-email
. Apply these changes using kubectl
.apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin-auth0-vs namespace: gloo-system spec: sslConfig: secretRef: name: upstream-tls namespace: gloo-system virtualHost: domains: - 'glootest.com' routes: - matchers: - prefix: / routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system options: extauth: configRef: name: auth0-ac namespace: gloo-system stagedTransformations: early: requestTransforms: - requestTransformation: transformationTemplate: extractors: token: header: 'cookie' regex: '.*id_token=([^;]*).*' subgroup: 1 headers: jwt: text: '{{ token }}' headerManipulation: requestHeadersToRemove: - "cookie" jwt: providers: auth0: issuer: https://solo-io.us.auth0.com/ tokenSource: headers: - header: Jwt claimsToHeaders: - claim: email header: x-solo-claim-email jwks: remote: url: https://solo-io.us.auth0.com/.well-known/jwks.json upstreamRef: name: auth0-jwks-upstream namespace: gloo-system
Jwt
header is removed and has been replaced by the X-Solo-Claim-Email
header, whose contents match the email
claim in the JWT from the Auth0 callback.Driving RBAC Decisions Using JWT Claims
email
user identity extracted from the JWT to drive authorization decisions. Let’s apply it to a simple use case where we only want to allow the jim.barton@solo.io
user to have access to the httpbin /get
endpoint but no others.kubectl
. Note in particular the new content in the rbac
stanza.apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin-auth0-vs namespace: gloo-system spec: sslConfig: secretRef: name: upstream-tls namespace: gloo-system virtualHost: domains: - 'glootest.com' routes: - matchers: - prefix: / routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system options: extauth: configRef: name: auth0-ac namespace: gloo-system stagedTransformations: early: requestTransforms: - requestTransformation: transformationTemplate: extractors: token: header: 'cookie' regex: '.*id_token=([^;]*).*' subgroup: 1 headers: jwt: text: '{{ token }}' headerManipulation: requestHeadersToRemove: - "cookie" jwt: providers: auth0: issuer: https://solo-io.us.auth0.com/ tokenSource: headers: - header: Jwt claimsToHeaders: - claim: email header: x-solo-claim-email jwks: remote: url: https://solo-io.us.auth0.com/.well-known/jwks.json upstreamRef: name: auth0-jwks-upstream namespace: gloo-system rbac: policies: viewer: permissions: methods: - GET pathPrefix: /get principals: - jwtPrincipal: claims: email: jim.barton@solo.io
email
in the rbac
policy, the https://glootest.com/get
endpoint will respond in the web browser exactly as before. However, authenticating as a different user or exercising a different endpoint will result in an access denied
error like this.Cache the JWT in Redis
- responds to initial calls on the Gloo endpoint by forwarding the user to an Auth0 authentication dialog;
- accepts a callback from Auth0 containing a JWT in a cookie header;
- extracts and validates the token from the cookie;
- extracts “interesting” claims from the JWT, like the end user’s email address;
- adds new request headers based on those claims; and
- applies RBAC rules to determine if the request should be authorized based on those headers.
AuthConfig
to use the Redis instance bundled with Gloo Edge to store our access token and simply hold a reference to it in a cookie named auth0-session
. Remember to customize the clientId and issuerUrl values to match your own Auth0 account. Note in particular the new session
stanza below. Apply these changes to your cluster.apiVersion: enterprise.gloo.solo.io/v1 kind: AuthConfig metadata: name: auth0-ac namespace: gloo-system spec: configs: - oauth2: oidcAuthorizationCode: appUrl: https://glootest.com callbackPath: /callback clientId: insert-your-client-id-here clientSecretRef: name: auth0-client-secret namespace: gloo-system issuerUrl: https://solo-io.us.auth0.com/ scopes: - email session: failOnFetchFailure: true redis: cookieName: auth0-session options: host: redis.gloo-system.svc.cluster.local:6379
VirtualService
that will enable us to see how Gloo Edge manages our token. Apply these changes to your cluster as well.apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin-auth0-vs namespace: gloo-system spec: sslConfig: secretRef: name: upstream-tls namespace: gloo-system virtualHost: domains: - 'glootest.com' routes: - matchers: - prefix: / routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system options: extauth: configRef: name: auth0-ac namespace: gloo-system
auth0-session
header as shown below. This session header is not the JWT itself, but a pointer to a Redis key that now securely holds the JWT.
Re-establish JWT header to drive RBAC decisions
apiVersion: enterprise.gloo.solo.io/v1 kind: AuthConfig metadata: name: auth0-ac namespace: gloo-system spec: configs: - oauth2: oidcAuthorizationCode: appUrl: https://glootest.com callbackPath: /callback clientId: clientSecretRef: name: auth0-client-secret namespace: gloo-system issuerUrl: https://solo-io.us.auth0.com/ scopes: - email session: failOnFetchFailure: true redis: cookieName: auth0-session options: host: redis.gloo-system.svc.cluster.local:6379 headers: id_token_header: "jwt"
Jwt
header re-appears in the list of headers returned from httpbin
.
Now we can restore our Virtual Service’s RBAC policies from the end of the previous session and confirm that they still work as before. But now we are doing in a more secure way that does not risk exceeding cookie size thresholds in the web browser.
apiVersion: gateway.solo.io/v1 kind: VirtualService metadata: name: httpbin-auth0-vs namespace: gloo-system spec: sslConfig: secretRef: name: upstream-tls namespace: gloo-system virtualHost: domains: - 'glootest.com' routes: - matchers: - prefix: / routeAction: single: upstream: name: default-httpbin-8000 namespace: gloo-system options: extauth: configRef: name: auth0-ac namespace: gloo-system headerManipulation: requestHeadersToRemove: - "cookie" jwt: providers: auth0: issuer: https://solo-io.us.auth0.com/ tokenSource: headers: - header: Jwt claimsToHeaders: - claim: email header: x-solo-claim-email jwks: remote: url: https://solo-io.us.auth0.com/.well-known/jwks.json upstreamRef: name: auth0-jwks-upstream namespace: gloo-system rbac: policies: viewer: permissions: methods: - GET pathPrefix: /get principals: - jwtPrincipal: claims: email: jim.barton@solo.io
X-Solo-Claim-Email
header, which was used by Gloo Edge to authorize the request. Try a different endpoint, such as https://glootest.com/base64/R2xvbyBpcyBhd2Vzb21lCg==
, and you will see the RBAC: access denied
message.Enable Logout Redirect to Remove Session Cookie
Allowed Logout URLs
option on the Auth0 application to point to a URL that the Gloo AuthConfig is expecting. You specify this in the AuthConfig by providing a logoutPath:
URI. One possibility is shown below.apiVersion: enterprise.gloo.solo.io/v1 kind: AuthConfig metadata: name: auth0-ac namespace: gloo-system spec: configs: - oauth2: oidcAuthorizationCode: appUrl: https://glootest.com callbackPath: /callback clientId: insert-your-app-client-id-here clientSecretRef: name: auth0-client-secret namespace: gloo-system issuerUrl: https://solo-io.us.auth0.com/ scopes: - email session: failOnFetchFailure: true redis: cookieName: auth0-session options: host: redis.gloo-system.svc.cluster.local:6379 headers: id_token_header: "jwt" logoutPath: /logout
Learn More
- Visit the website and read the docs
- Request a live demo or trial
- See video content on the solo.io YouTube channel
- Questions? Join the Solo.io Slack community