Advanced Authentication Workflows with OpenID Connect using Gloo API Gateway – Part 2

In the previous Blog post, we covered how Gloo can be used to authenticate users with OIDC and how it can process the JWT token returned by the identity provider.

The JWT token was stored as a cookie in the web browser and sent with each HTTP request as a header.

We configured Gloo to manipulate each request as follow:

  • extract the token from the cookie header
  • validate the token
  • extract claims from the token
  • add new headers based on the claims (for example, a header with the email of the user)
  • apply RBAC rules to determine if the request should be authorized (based on the email, method, path, …)

This workflow works well, but it has both pros and cons:

  • pro: the cookie is sent with each HTTP request (because it’s stored as a cookie), so we can do all these nice manipulations.
  • con: even if the cookie is stored with the attributes Secure and HttpOnly, some users prefer to avoid any sensitive information in the web browser.
  • con: in some cases, there are many claims in the token which can cause the size of the cookie to exceed the 4 KB allowed by the web browers.

Caching the JWT token in Redis

To avoid storing the cookie in the web browser, you can enable caching when creating the Gloo AuthConfig:

apiVersion: enterprise.gloo.solo.io/v1
kind: AuthConfig
metadata:
  name: google-oidc
  namespace: gloo-system
spec:
  configs:
  - oauth2:
      oidcAuthorizationCode:
        appUrl: https://mydomain.com
        callbackPath: /callback
        clientId: $CLIENT_ID
        clientSecretRef:
          name: google
          namespace: gloo-system
        issuerUrl: https://accounts.google.com
        scopes:
        - email
        session:
          failOnFetchFailure: true
          redis:
            cookieName: session
            options:
              host: redis.gloo-system.svc.cluster.local:6379

As you can see, the token will be stored in Redis.

Let’s start with a simple VirtualService:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: default
  namespace: gloo-system
spec:
  sslConfig:
    secretRef:
      name: upstream-tls
      namespace: gloo-system
  virtualHost:
    domains:
    - '*'
    routes:
    - matchers:
      - prefix: /
      routeAction:
        single:
          upstream:
            name: default-httpbin-8000
            namespace: gloo-system
    options:
      extauth:
        configRef:
          name: google-oidc
          namespace: gloo-system

If you try to access the application using your browser, here is the output you should get:

{
  "args": {}, 
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "fr-fr", 
    "Content-Length": "0", 
    "Cookie": "session=6DDMJFK2P35EQ42LZFUSBBHTZ3Y5X6CWKNJ33HHDSN2PW3HFNRGYN47UKM5BOE362VGSMW4VCMK7ZNOK7D45EXIKKDG3HGJOWXL7SWQ=", 
    "Host": "mydomain.com", 
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15", 
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 
    "X-User-Id": "https://accounts.google.com;117577190223680183681"
  }, 
  "origin": "192.168.149.8", 
  "url": "https://mydomain.com/get"
}

The web browser is still sending a cookie with each request, but this cookie doesn’t contain the token anymore.

It a session cookie that allows Gloo to retrieve the JWT token from Redis.

Everything works well, but we can’t access the token anymore and perform all the nice manipulations we were able to perform before. So, how will the backend application able to determine who the user is if it needs this information ?

Forward the JWT token upstream

Gloo provides an option to create a new header with this token when it retrieves it from Redis.

Let’s update the AuthConfig to enable this option:

apiVersion: enterprise.gloo.solo.io/v1
kind: AuthConfig
metadata:
  name: google-oidc
  namespace: gloo-system
spec:
  configs:
  - oauth2:
      oidcAuthorizationCode:
        appUrl: https://mydomain.com
        callbackPath: /callback
        clientId: $CLIENT_ID
        clientSecretRef:
          name: google
          namespace: gloo-system
        issuerUrl: https://accounts.google.com
        scopes:
        - email
        session:
          failOnFetchFailure: true
          redis:
            cookieName: session
            options:
              host: redis.gloo-system.svc.cluster.local:6379
        headers:
          id_token_header: "jwt"

Here is the output you should get if you refresh the web page:

{
  "args": {}, 
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "fr-fr", 
    "Content-Length": "0", 
    "Cookie": "session=6DDMJFK2P35EQ42LZFUSBBHTZ3Y5X6CWKNJ33HHDSN2PW3HFNRGYN47UKM5BOE362VGSMW4VCMK7ZNOK7D45EXIKKDG3HGJOWXL7SWQ=", 
    "Host": "mydomain.com", 
    "Jwt": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImYwOTJiNjEyZTliNjQ0N2RlYjEwNjg1YmI4ZmZhOGFlNjJmNmFhOTEiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJhenAiOiI2MzUzODgzNDA1MjEtMHNtNzFtZ29rZXFmbGs0ajlyZGJncHQyNnRkbGJybjMuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJhdWQiOiI2MzUzODgzNDA1MjEtMHNtNzFtZ29rZXFmbGs0ajlyZGJncHQyNnRkbGJybjMuYXBwcy5nb29nbGV1c2VyY29udGVudC5jb20iLCJzdWIiOiIxMTc1NzcxOTAyMjM2ODAxODM2ODEiLCJlbWFpbCI6ImRqYW5ub3RAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJOWHVKNWpKZnhkTGVhTHFDLUtPMF9nIiwiaWF0IjoxNjA0NTY1MDg2LCJleHAiOjE2MDQ1Njg2ODZ9.wFrDI_pXNgOzOWKEt0ou0MNfsWUbVWP4KtnkyuqET38wia6c3gX4g5Ck2GPHBy1ZuKC9luc1SK3n11G9VvhmiR94ysk6kWJDtK1uEYv6MNt4KLFtiO9iQ0Mx8yiqXgKPFdGEssqLbE7ifQtko5YJA8qARI6OwPxHt3FwjHYI8G_qJmiTqcJGwXi5yPj47rXuwUc7akEwFga5w4EP46Xsv7onxnoMbeM8w4zJNncVzm9qruf56aFGm9vWgCl3cdIV6fDZfFZkBrgt4r4S-UADfrqqogvSe9RajXxkiln_TKtlrVXha_--pfvrhgf4uqyY4sfwTj2qrjHsiDUNbDbBMQ", 
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15", 
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 
    "X-User-Id": "https://accounts.google.com;117577190223680183681"
  }, 
  "origin": "192.168.149.8", 
  "url": "https://mydomain.com/get"
}

Great ! Now you have your JWT token in a header and you can perform all the manipulations like in the previous Blog post.

Update your VirtualService as follow:

apiVersion: gateway.solo.io/v1
kind: VirtualService
metadata:
  name: default
  namespace: gloo-system
spec:
  sslConfig:
    secretRef:
      name: upstream-tls
      namespace: gloo-system
  virtualHost:
    domains:
    - '*'
    routes:
    - matchers:
      - prefix: /
      routeAction:
        single:
          upstream:
            name: default-httpbin-8000
            namespace: gloo-system
    options:
      extauth:
        configRef:
          name: google-oidc
          namespace: gloo-system
      headerManipulation:
        requestHeadersToRemove:
        - "cookie"
      jwt:
        providers:
          google:
            issuer: https://accounts.google.com
            tokenSource:
              headers:
              - header: Jwt
            claimsToHeaders:
            - claim: email
              header: x-solo-claim-email
            - claim: email_verified
              header: x-solo-claim-email-verified
            jwks:
              remote:
                url: https://www.googleapis.com/oauth2/v3/certs
                upstreamRef:
                  name: google-jkws
                  namespace: gloo-system
      rbac:
        policies:
          viewer:
            permissions:
              methods:
              - GET
              pathPrefix: /get
            principals:
            - jwtPrincipal:
                claims:
                  email: djannot@gmail.com

Here is the output you should get if you refresh the web page:

{
  "args": {}, 
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 
    "Accept-Encoding": "gzip, deflate, br", 
    "Accept-Language": "fr-fr", 
    "Content-Length": "0", 
    "Host": "mydomain.com", 
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15", 
    "X-Envoy-Expected-Rq-Timeout-Ms": "15000", 
    "X-Solo-Claim-Email": "djannot@gmail.com", 
    "X-Solo-Claim-Email-Verified": "true", 
    "X-User-Id": "https://accounts.google.com;117577190223680183681"
  }, 
  "origin": "192.168.149.8", 
  "url": "https://mydomain.com/get"
}

What happens here ?

Gloo gets the token from the Jwt header, validates it, extracts some claims from it and add headers for these claims. Finally, it applies RBAC rules based on the claims.

So, we now have the best of both worlds: the JWT token isn’t stored in the web browser anymore, but we can perform all the manipulations we want from it.

Logout

Gloo also supports specifying a logout url. When specified, accessing this url will trigger a deletion of the user session.

You can enable this option by updating the AuthConfig as follow:

apiVersion: enterprise.gloo.solo.io/v1
kind: AuthConfig
metadata:
  name: google-oidc
  namespace: gloo-system
spec:
  configs:
  - oauth2:
      oidcAuthorizationCode:
        appUrl: https://mydomain.com
        callbackPath: /callback
        clientId: $CLIENT_ID
        clientSecretRef:
          name: google
          namespace: gloo-system
        issuerUrl: https://accounts.google.com
        scopes:
        - email
        session:
          failOnFetchFailure: true
          redis:
            cookieName: session
            options:
              host: redis.gloo-system.svc.cluster.local:6379
        headers:
          id_token_header: "x-token"
        logoutPath: /logout

Get Started with Gloo

Gloo is available in open source and enterprise editions addressing a wide range of edge and API gateway use cases. Learn more about Gloo by visiting the additional resources below.