diff --git a/slides/k8s/openid-connect.md b/slides/k8s/openid-connect.md
new file mode 100644
index 00000000..e1f4786e
--- /dev/null
+++ b/slides/k8s/openid-connect.md
@@ -0,0 +1,377 @@
+# OpenID Connect
+
+- The Kubernetes API server can perform authentication with OpenID connect
+
+- This requires an *OpenID provider*
+
+ (external authorization server using the OAuth 2.0 protocol)
+
+- We can use a third-party provider (e.g. Google) or run our own (e.g. Dex)
+
+- We are going to give an overview of the protocol
+
+- We will show it in action (in a simplified scenario)
+
+---
+
+## Workflow overview
+
+- We want to access our resources (a Kubernetes cluster)
+
+- We authenticate with the OpenID provider
+
+ - we can do this directly (e.g. by going to https://accounts.google.com)
+
+ - or maybe a kubectl plugin can open a browser page on our behalf
+
+- After authenticating us, the OpenID provider gives us:
+
+ - an *id token* (a short-lived signed JSON Web Token, see next slide)
+
+ - a *refresh token* (to renew the previous one when needed)
+
+- We can now issue requests to the Kubernetes API with the *id token*
+
+- The API server will verify that token's content to authenticate us
+
+---
+
+## JSON Web Tokens
+
+- A JSON Web Token (JWT) has three parts:
+
+ - a header specifying algorithms and token type
+
+ - a payload (indicating who issued the token, for whom, which purposes...)
+
+ - a signature generated by the issuer (the issuer = the OpenID provider)
+
+- Anyone can verify a JWT without contacting the issuer
+
+ (except to obtain the issuer's public key)
+
+- Pro tip: we can inspect a JWT with https://jwt.io/
+
+---
+
+## How the Kubernetes API uses JWT
+
+- Server side
+
+ - enable OIDC authentication
+
+ - indicate which issuer (provider) should be allowed
+
+ - indicate which audience (or "client id") should be allowed
+
+ - if necessary, map or prefix user and group names
+
+- Client side
+
+ - obtain JWT as described earlier
+
+ - pass JWT as authentication token
+
+ - renew JWT when needed (using the refresh token)
+
+---
+
+## Demo time!
+
+- We will use [Google Accounts](https://accounts.google.com) as our OpenID provider
+
+- We will use the [Google OAuth Playground](https://developers.google.com/oauthplayground) as the "audience" or "client id"
+
+- We will obtain a JWT through Google Accounts and the OAuth Playground
+
+- We will enable OIDC in the Kubernetes API server
+
+- We will use the JWT to authenticate
+
+.footnote[If you can't or won't use a Google Account, you can try to adapt to another provider.]
+
+---
+
+## Checking the API server logs
+
+- The API server logs will be particularly useful in this section
+
+ (they will indicate e.g. why a specific token is rejected)
+
+- Let's keep an eye on the API server output!
+
+.exercise[
+
+- Tail the logs of the API server:
+ ```bash
+ kubectl logs kube-apiserver-node1 --follow --namespace=kube-system
+ ```
+
+]
+
+---
+
+## Authenticate with the OpenID provider
+
+- We will use the Google OAuth Playground for convenience
+
+- In a real scenario, we would need our own OAuth client instead of the playground
+
+ (even if we were still using Google as the OpenID provider)
+
+.exercise[
+
+- Open the Google OAuth Playground:
+ ```
+ https://developers.google.com/oauthplayground/
+ ```
+
+- Enter our own custom scope in the text field:
+ ```
+ https://www.googleapis.com/auth/userinfo.email
+ ```
+
+- Click on "Authorize APIs" and allow the playground to access our email address
+
+]
+
+---
+
+## Obtain our JSON Web Token
+
+- The previous step gave us an "authorization code"
+
+- We will use it to obtain tokens
+
+.exercise[
+
+- Click on "Exchange authorization code for tokens"
+
+]
+
+- The JWT is the very long `id_token` that shows up on the right hand side
+
+ (it is a base64-encoded JSON object, and should therefore start with `eyJ`)
+
+---
+
+## Using our JSON Web Token
+
+- We need to create a context (in kubeconfig) for our token
+
+ (if we just add the token or use `kubectl --token`, our certificate will still be used)
+
+.exercise[
+
+- Create a new authentication section in kubeconfig:
+ ```bash
+ kubectl config set-credentials myjwt --token=eyJ...
+ ```
+
+- Try to use it:
+ ```bash
+ kubectl --user=myjwt get nodes
+ ```
+
+]
+
+We should get an `Unauthorized` response, since we haven't enabled OpenID Connect in the API server yet. We should also see `invalid bearer token` in the API server log output.
+
+---
+
+## Enabling OpenID Connect
+
+- We need to add a few flags to the API server configuration
+
+- These two are mandatory:
+
+ `--oidc-issuer-url` → URL of the OpenID provider
+
+ `--oidc-client-id` → app requesting the authentication
+
(in our case, that's the ID for the Google OAuth Playground)
+
+- This one is optional:
+
+ `--oidc-username-claim` → which field should be used as user name
+
(we will use the user's email address instead of an opaque ID)
+
+- See the [API server documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server
+) for more details about all available flags
+
+---
+
+## Updating the API server configuration
+
+- The instructions below will work for clusters deployed with kubeadm
+
+ (or where the control plane is deployed in static pods)
+
+- If your cluster is different, you will need to adapt them
+
+.exercise[
+
+- Edit `/etc/kubernetes/manifests/kube-apiserver.yaml`
+
+- Add the following lines to the list of command-line flags:
+ ```yaml
+ - --oidc-issuer-url=https://accounts.google.com
+ - --oidc-client-id=407408718192.apps.googleusercontent.com
+ - --oidc-username-claim=email
+ ```
+]
+
+---
+
+## Restarting the API server
+
+- The kubelet monitors the files in `/etc/kubernetes/manifests`
+
+- When we save the pod manifest, kubelet will restart the corresponding pod
+
+ (using the updated command line flags)
+
+.exercise[
+
+- After making the changes described on the previous slide, save the file
+
+- Issue a simple command (like `kubectl version`) until the API server is back up
+
+ (it might take between a few seconds and one minute for the API server to restart)
+
+- Restart the `kubectl logs` command to view the logs of the API server
+
+]
+
+---
+
+## Using our JSON Web Token
+
+- Now that the API server is set up to recognize our token, try again!
+
+.exercise[
+
+- Try an API command with our token:
+ ```bash
+ kubectl --user=myjwt get nodes
+ kubectl --user=myjwt get pods
+ ```
+
+]
+
+We should see a message like:
+```
+Error from server (Forbidden): nodes is forbidden: User "jean.doe@gmail.com"
+cannot list resource "nodes" in API group "" at the cluster scope
+```
+
+→ We were successfully *authenticated*, but not *authorized*.
+
+---
+
+## Authorizing our user
+
+- As an extra step, let's grant read access to our user
+
+- We will use the pre-defined ClusterRole `view`
+
+.exercise[
+
+- Create a ClusterRoleBinding allowing us read access to the cluster:
+ ```bash
+ kubectl create clusterrolebinding i-can-view \
+ --user=`jean.doe@gmail.com` --clusterrole=view
+ ```
+
+- Confirm that we can now list pods with our token:
+ ```bash
+ kubectl --user=myjwt get pods
+ ```
+
+]
+
+---
+
+## From demo to production
+
+.warning[This was a very simplified demo! In a real deployment...]
+
+- We wouldn't use the Google OAuth Playground
+
+- We *probably* wouldn't even use Google at all
+
+ (it doesn't seem to provide a way to include groups!)
+
+- Some popular alternatives:
+
+ - [Dex](https://github.com/dexidp/dex),
+ [Keycloak](https://www.keycloak.org/)
+ (self-hosted)
+
+ - [Okta](https://developer.okta.com/docs/how-to/creating-token-with-groups-claim/#step-five-decode-the-jwt-to-verify)
+ (SaaS)
+
+- We would use a helper (like the [kubelogin](https://github.com/int128/kubelogin) plugin) to automatically obtain tokens
+
+---
+
+class: extra-details
+
+## Service Account tokens
+
+- The tokens used by Service Accounts are JWT tokens as well
+
+- They are signed and verified using a special service account key pair
+
+.exercise[
+
+- Extract the token of a service account in the current namespace:
+ ```bash
+ kubectl get secrets -o jsonpath={..token} | base64 -d
+ ```
+
+- Copy-paste the token to a verification service like https://jwt.io
+
+- Notice that it says "Invalid Signature"
+
+]
+
+---
+
+class: extra-details
+
+## Verifying Service Account tokens
+
+- JSON Web Tokens embed the URL of the "issuer" (=OpenID provider)
+
+- The issuer provides its public key through a well-known discovery endpoint
+
+ (similar to https://accounts.google.com/.well-known/openid-configuration)
+
+- There is no such endpoint for the Service Account key pair
+
+- But we can provide the public key ourselves for verification
+
+---
+
+class: extra-details
+
+## Verifying a Service Account token
+
+- On clusters provisioned with kubeadm, the Service Account key pair is:
+
+ `/etc/kubernetes/pki/sa.key` (used by the controller manager to generate tokens)
+
+ `/etc/kubernetes/pki/sa.pub` (used by the API server to validate the same tokens)
+
+.exercise[
+
+- Display the public key used to sign Service Account tokens:
+ ```bash
+ sudo cat /etc/kubernetes/pki/sa.pub
+ ```
+
+- Copy-paste the key in the "verify signature" area on https://jwt.io
+
+- It should now say "Signature Verified"
+
+]
diff --git a/slides/kube-fullday.yml b/slides/kube-fullday.yml
index f5462d85..1ce65170 100644
--- a/slides/kube-fullday.yml
+++ b/slides/kube-fullday.yml
@@ -58,6 +58,7 @@ chapters:
#- k8s/netpol.md
#- k8s/authn-authz.md
#- k8s/csr-api.md
+ #- k8s/openid-connect.md
#- k8s/podsecuritypolicy.md
#- k8s/ingress.md
#- k8s/gitworkflows.md
diff --git a/slides/kube-selfpaced.yml b/slides/kube-selfpaced.yml
index 59eb8377..25cc5fbc 100644
--- a/slides/kube-selfpaced.yml
+++ b/slides/kube-selfpaced.yml
@@ -58,6 +58,7 @@ chapters:
- - k8s/netpol.md
- k8s/authn-authz.md
- k8s/csr-api.md
+ - k8s/openid-connect.md
- k8s/podsecuritypolicy.md
- - k8s/ingress.md
- k8s/gitworkflows.md
diff --git a/slides/kube-twodays.yml b/slides/kube-twodays.yml
index e338b627..243da6b5 100644
--- a/slides/kube-twodays.yml
+++ b/slides/kube-twodays.yml
@@ -58,6 +58,7 @@ chapters:
#- k8s/netpol.md
- k8s/authn-authz.md
- k8s/csr-api.md
+ - k8s/openid-connect.md
- k8s/podsecuritypolicy.md
- - k8s/ingress.md
#- k8s/gitworkflows.md