Files
dex/examples/example-app/server/token.go
Maksim Nabokikh 6f2e233c7a feat: example app session refactoring (#4712)
Signed-off-by: maksim.nabokikh <max.nabokih@gmail.com>
2026-04-02 14:19:10 +02:00

164 lines
4.1 KiB
Go

package server
import (
"bytes"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"math/big"
"net/http"
"net/url"
"time"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
"github.com/dexidp/dex/examples/example-app/session"
)
// handleTokenRefresh redeems a refresh token for a new token set.
func (s *Server) handleTokenRefresh(w http.ResponseWriter, r *http.Request) {
refresh := r.FormValue("refresh_token")
if refresh == "" {
http.Error(w, fmt.Sprintf("no refresh_token in request: %q", r.Form), http.StatusBadRequest)
return
}
ctx := oidc.ClientContext(r.Context(), s.client)
t := &oauth2.Token{
RefreshToken: refresh,
Expiry: time.Now().Add(-time.Hour),
}
token, err := s.oauth2Config(nil).TokenSource(ctx, t).Token()
if err != nil {
http.Error(w, fmt.Sprintf("failed to get token: %v", err), http.StatusInternalServerError)
return
}
s.renderTokenResult(w, r, token)
}
// renderTokenResult verifies an ID token, extracts claims, and renders the token page.
func (s *Server) renderTokenResult(w http.ResponseWriter, r *http.Request, token *oauth2.Token) {
rawIDToken, ok := token.Extra("id_token").(string)
if !ok {
http.Error(w, "no id_token in token response", http.StatusInternalServerError)
return
}
idToken, err := s.verifier.Verify(r.Context(), rawIDToken)
if err != nil {
http.Error(w, fmt.Sprintf("failed to verify ID token: %v", err), http.StatusInternalServerError)
return
}
accessToken, ok := token.Extra("access_token").(string)
if !ok {
accessToken = token.AccessToken
if accessToken == "" {
http.Error(w, "no access_token in token response", http.StatusInternalServerError)
return
}
}
// Persist claims for session-aware index page and logout.
var uc session.UserClaims
_ = idToken.Claims(&uc)
s.auth.Set(&uc, rawIDToken)
claims, err := encodeIDTokenClaims(idToken)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
s.renderer.RenderTokenPage(w, TokenPageData{
IDToken: rawIDToken,
IDTokenJWTLink: jwtIOLink(rawIDToken),
AccessToken: accessToken,
AccessTokenJWTLink: jwtIOLink(accessToken),
RefreshToken: token.RefreshToken,
RedirectURL: s.redirectURI,
Claims: claims,
PublicKeyPEM: s.fetchPublicKeyPEM(),
})
}
// encodeIDTokenClaims extracts and pretty-prints the claims from an ID token.
func encodeIDTokenClaims(idToken *oidc.IDToken) (string, error) {
var claims json.RawMessage
if err := idToken.Claims(&claims); err != nil {
return "", fmt.Errorf("error decoding ID token claims: %v", err)
}
buf := new(bytes.Buffer)
if err := json.Indent(buf, claims, "", " "); err != nil {
return "", fmt.Errorf("error indenting ID token claims: %v", err)
}
return buf.String(), nil
}
// jwtIOLink creates a jwt.io debugger URL for the given token.
func jwtIOLink(token string) string {
return "https://jwt.io/#debugger-io?token=" + url.QueryEscape(token)
}
// fetchPublicKeyPEM fetches the provider's JWKS and returns the first RSA public key as PEM.
func (s *Server) fetchPublicKeyPEM() string {
if s.jwksURL == "" {
return ""
}
resp, err := s.client.Get(s.jwksURL)
if err != nil {
return ""
}
defer resp.Body.Close()
var jwks struct {
Keys []json.RawMessage `json:"keys"`
}
if err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil || len(jwks.Keys) == 0 {
return ""
}
var key struct {
N string `json:"n"`
E string `json:"e"`
Kty string `json:"kty"`
}
if err := json.Unmarshal(jwks.Keys[0], &key); err != nil || key.Kty != "RSA" {
return ""
}
nBytes, err1 := base64.RawURLEncoding.DecodeString(key.N)
eBytes, err2 := base64.RawURLEncoding.DecodeString(key.E)
if err1 != nil || err2 != nil {
return ""
}
var eInt int
for _, b := range eBytes {
eInt = eInt<<8 | int(b)
}
pubKey := &rsa.PublicKey{
N: new(big.Int).SetBytes(nBytes),
E: eInt,
}
pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return ""
}
return string(pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKeyBytes,
}))
}