This topic tells you how to configure token expiry settings for Application Single Sign-On (commonly called AppSSO).
AppSSO allows you to optionally configure the token expiry settings in your AuthServer
resource.
The default token expiry settings are as follows:
Token type | Lifetime |
---|---|
Access token | 12 hours |
Identity token | 12 hours |
Refresh token | 720 hours or 30 days |
VMware recommends setting a shorter lifetime for access tokens, typically measured in hours, and a longer lifetime for refresh tokens, typically measured in days. Refresh tokens acquire new access tokens, so they have a longer lifespan.
To override the token expiry settings, configure the following in your AuthServer
resource:
kind: AuthServer
# ...
spec:
token:
accessToken:
expiry: "12h"
idToken:
expiry: "12h"
refreshToken:
expiry: "720h"
expiry
field examples:
Type | Example | Definition |
---|---|---|
Seconds | 10s |
10 seconds |
Minutes | 10m |
10 minutes |
Hours | 10h |
10 hours |
NoteThe
expiry
field adheres to the duration constraints of the Go standard time library and does not support durations in units beyond hours, such as days or weeks. For more information, see the Go documentation.
The token expiry constraints are as follows:
expiry
field cannot be negative or zero.After you set up an Application Single Sign-On AuthServer
, you can verify that the token received by applications looks as expected. For this purpose, you can create a simple application consuming your AuthServer
. The following YAML file creates such an application. When you access its URL, it enables you to log in by using your AuthServer
and displays the token it receives.
Caution
- The simple application is not intended for production use. It only serves a tool to help you verify your setup.
- The following YAML file pulls an unvetted public image
bitnami/oauth2-proxy:7.3.0
- This section does not apply to an air-gapped environment.
If you stored the following YAML in a file named token-viewer.yaml
, you can apply it to your cluster by running the following command:
ytt -f token-viewer.yaml --data-value ingress_domain=YOUR-INGRESS-DOMAIN --data-value-yaml 'authserver_selector=YOUR-AUTHSERVER-SELECTOR' | kubectl apply -f-
Where YOUR-AUTHSERVER-SELECTOR
is the label name and its value. For example: {"name": "ci"}
.
A full example is as follows:
#!
#! Token viewer
#!
#! usage:
#!
#! ytt -f token-viewer.yaml \
#! --data-value ingress_domain=example.com \
#! --data-value-yaml 'authserver_selector={"name": "ci"}' \
#! --data-value namespace=default
#!
#! Then navigate to http://token-viewer.<INGRESS_DOMAIN>
#!
#@ load("@ytt:data", "data")
#@ fqdn = "token-viewer." + data.values.ingress_domain
#@ redirect_uri = "http://" + fqdn + "/oauth2/callback"
#@ namespace = data.values.namespace if "namespace" in data.values else "default"
---
apiVersion: sso.apps.tanzu.vmware.com/v1alpha1
kind: ClientRegistration
metadata:
name: my-client-registration
namespace: #@ namespace
spec:
authServerSelector:
matchLabels: #@ data.values.authserver_selector
redirectURIs:
- #@ redirect_uri
requireUserConsent: false
clientAuthenticationMethod: client_secret_basic
authorizationGrantTypes:
- "authorization_code"
scopes:
- name: "openid"
- name: "email"
- name: "profile"
- name: "roles"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: token-viewer
namespace: #@ namespace
spec:
replicas: 1
selector:
matchLabels:
name: token-viewer
template:
metadata:
labels:
name: token-viewer
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- image: bitnami/oauth2-proxy:7.3.0
name: proxy
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
ports:
- containerPort: 4180
name: proxy-port
protocol: TCP
env:
- name: ISSUER_URI
valueFrom:
secretKeyRef:
name: my-client-registration
key: issuer-uri
- name: CLIENT_ID
valueFrom:
secretKeyRef:
name: my-client-registration
key: client-id
- name: CLIENT_SECRET
valueFrom:
secretKeyRef:
name: my-client-registration
key: client-secret
command: [ "oauth2-proxy" ]
args:
- --oidc-issuer-url=$(ISSUER_URI)
- --client-id=$(CLIENT_ID)
- --insecure-oidc-skip-issuer-verification=true
- --client-secret=$(CLIENT_SECRET)
- --cookie-secret=0000000000000000
- --cookie-secure=false
- --http-address=http://:4180
- --provider=oidc
- --scope=openid email profile roles
- --email-domain=*
- --insecure-oidc-allow-unverified-email=true
- --oidc-groups-claim=roles
- --upstream=http://127.0.0.1:8000
- #@ "--redirect-url=" + redirect_uri
- --ssl-upstream-insecure-skip-verify=true
- --ssl-insecure-skip-verify=true
- --skip-provider-button=true
- --pass-authorization-header=true
- --prefer-email-to-user=true
- #@ "--whitelist-domain=*." + data.values.ingress_domain
- image: python:3.9
name: application
securityContext:
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
resources:
limits:
cpu: 100m
memory: 100Mi
env:
- name: ISSUER_URI
valueFrom:
secretKeyRef:
name: my-client-registration
key: issuer-uri
command: [ "python" ]
args:
- -c
- |
from http.server import HTTPServer, BaseHTTPRequestHandler
import base64
import json
import os
from urllib.parse import quote_plus
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == "/token":
self.token()
elif self.path == "/jwt":
self.jwt()
else:
self.greet()
def greet(self):
username = self.headers.get("x-forwarded-user")
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
# This uses the authserver's unsupported RP-initiated logout
token = self.headers.get("Authorization").split("Bearer ")[-1]
authserver_logout_uri = quote_plus(os.environ.get("ISSUER_URI") + "/connect/logout?id_token_hint=" + token)
logout_uri = f"/oauth2/sign_out?rd={authserver_logout_uri}"
page = f"""
<h1>It Works!</h1>
<p>You are logged in as <b>{username}</b></p>
<p><a href="/jwt">Show me my id_token (JWT format)</a></p>
<p><a href="/token">Show me my id_token (JSON format)</a></p>
<p><a href="{logout_uri}">Log out</a></p>
"""
self.wfile.write(page.encode("utf-8"))
def token(self):
token = self.headers.get("Authorization").split("Bearer ")[-1]
payload = token.split(".")[1].replace("-","+").replace("_","/")
decoded = base64.b64decode(bytes(payload, "utf-8") + b'==').decode("utf-8")
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(decoded.encode("utf-8"))
def jwt(self):
token = self.headers.get("Authorization").split("Bearer ")[-1]
self.send_response(200)
self.send_header("Content-type", "text/plain")
self.end_headers()
self.wfile.write(token.encode("utf-8"))
server_address = ('', 8000)
httpd = HTTPServer(server_address, Handler)
httpd.serve_forever()
---
apiVersion: v1
kind: Service
metadata:
name: token-viewer
namespace: #@ namespace
spec:
ports:
- port: 80
targetPort: proxy-port
name: proxy-svc-port
selector:
name: token-viewer
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: token-viewer
namespace: #@ namespace
spec:
rules:
- host: #@ fqdn
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: token-viewer
port:
name: proxy-svc-port