From f36848babe71d4d2d813775d9f57f4a89c07a630 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Mon, 23 Aug 2021 09:59:15 +0200 Subject: [PATCH] feat: validate id_token in auth code flow Co-authored-by: Kent Daleng --- cmd/wonderwall/main.go | 17 +++++++++++---- go.mod | 1 + go.sum | 2 ++ pkg/auth/validate.go | 27 ++++++++++++++++++++++++ pkg/router/router.go | 47 ++++++++++++++++++++++++++++++++++-------- 5 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 pkg/auth/validate.go diff --git a/cmd/wonderwall/main.go b/cmd/wonderwall/main.go index 156472e..e4ac51c 100644 --- a/cmd/wonderwall/main.go +++ b/cmd/wonderwall/main.go @@ -1,15 +1,19 @@ package main import ( + "context" + "net/http" + "os" + + "github.com/coreos/go-oidc" "github.com/nais/liberator/pkg/conftools" + log "github.com/sirupsen/logrus" + "golang.org/x/oauth2" + "github.com/nais/wonderwall/pkg/config" "github.com/nais/wonderwall/pkg/cryptutil" "github.com/nais/wonderwall/pkg/logging" "github.com/nais/wonderwall/pkg/router" - log "github.com/sirupsen/logrus" - "golang.org/x/oauth2" - "net/http" - "os" ) var maskedConfig = []string{ @@ -59,6 +63,11 @@ func run() error { Crypter: crypt, OauthConfig: oauthConfig, UpstreamHost: cfg.UpstreamHost, + IdTokenVerifier: oidc.NewVerifier( + cfg.IDPorten.WellKnown.Issuer, + oidc.NewRemoteKeySet(context.Background(), cfg.IDPorten.WellKnown.JwksURI), + &oidc.Config{ClientID: cfg.IDPorten.ClientID}, + ), } handler.Init() diff --git a/go.mod b/go.mod index 62ef464..fa42fe5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/nais/wonderwall go 1.16 require ( + github.com/coreos/go-oidc v2.1.0+incompatible github.com/go-chi/chi v1.5.4 github.com/nais/liberator v0.0.0-20210809103005-edb0141d646d github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect diff --git a/go.sum b/go.sum index 29b4edd..8a2483b 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,7 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -374,6 +375,7 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= diff --git a/pkg/auth/validate.go b/pkg/auth/validate.go new file mode 100644 index 0000000..0b8897b --- /dev/null +++ b/pkg/auth/validate.go @@ -0,0 +1,27 @@ +package auth + +import ( + "context" + "fmt" + + "github.com/coreos/go-oidc" + "golang.org/x/oauth2" +) + +func ValidateIdToken(ctx context.Context, verifier *oidc.IDTokenVerifier, token *oauth2.Token, nonce string) error { + raw, ok := token.Extra("id_token").(string) + if !ok { + return fmt.Errorf("missing id_token in token response") + } + + idToken, err := verifier.Verify(ctx, raw) + if err != nil { + return err + } + + if idToken.Nonce != nonce { + return fmt.Errorf("nonce does not match") + } + + return nil +} diff --git a/pkg/router/router.go b/pkg/router/router.go index d11d1ee..c1df6e2 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -7,18 +7,23 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/go-chi/chi/middleware" - "github.com/nais/wonderwall/pkg/cryptutil" - log "github.com/sirupsen/logrus" "io" "net/http" "net/url" "time" + "github.com/coreos/go-oidc" + "github.com/go-chi/chi/middleware" + log "github.com/sirupsen/logrus" + + "github.com/nais/wonderwall/pkg/auth" + "github.com/nais/wonderwall/pkg/cryptutil" + "github.com/go-chi/chi" - "github.com/nais/wonderwall/pkg/config" "golang.org/x/oauth2" "gopkg.in/square/go-jose.v2" + + "github.com/nais/wonderwall/pkg/config" ) const ( @@ -26,15 +31,17 @@ const ( ScopeOpenID = "openid" SessionCookieName = "io.nais.wonderwall.session" StateCookieName = "io.nais.wonderwall.state" + NonceCookieName = "io.nais.wonderwall.nonce" CodeVerifierCookieName = "io.nais.wonderwall.code_verifier" ) type Handler struct { - Config config.IDPorten - OauthConfig oauth2.Config - Crypter cryptutil.Crypter - UpstreamHost string - sessions map[string]*oauth2.Token + Config config.IDPorten + OauthConfig oauth2.Config + Crypter cryptutil.Crypter + UpstreamHost string + IdTokenVerifier *oidc.IDTokenVerifier + sessions map[string]*oauth2.Token } type loginParams struct { @@ -42,6 +49,7 @@ type loginParams struct { state string codeVerifier string url string + nonce string } func (h *Handler) Init() { @@ -102,6 +110,7 @@ func (h *Handler) LoginURL() (*loginParams, error) { return &loginParams{ session: base64.RawURLEncoding.EncodeToString(session), state: base64.RawURLEncoding.EncodeToString(state), + nonce: base64.RawURLEncoding.EncodeToString(nonce), codeVerifier: string(codeVerifier), url: u.String(), }, nil @@ -129,6 +138,12 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { return } + err = h.setEncryptedCookie(w, NonceCookieName, params.nonce) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + err = h.setEncryptedCookie(w, CodeVerifierCookieName, params.codeVerifier) if err != nil { w.WriteHeader(http.StatusInternalServerError) @@ -228,6 +243,13 @@ func (h *Handler) Callback(w http.ResponseWriter, r *http.Request) { return } + nonce, err := h.getEncryptedCookie(r, NonceCookieName) + if err != nil { + log.Error(err) + w.WriteHeader(http.StatusUnauthorized) + return + } + codeVerifier, err := h.getEncryptedCookie(r, CodeVerifierCookieName) if err != nil { log.Error(err) @@ -268,6 +290,13 @@ func (h *Handler) Callback(w http.ResponseWriter, r *http.Request) { return } + err = auth.ValidateIdToken(r.Context(), h.IdTokenVerifier, token, nonce) + if err != nil { + log.Error(err) + w.WriteHeader(http.StatusUnauthorized) + return + } + h.sessions[sessionCookie.Value] = token http.Redirect(w, r, "/", http.StatusTemporaryRedirect)