mirror of
https://github.com/nais/wonderwall.git
synced 2026-05-20 07:12:48 +00:00
feat: validate id_token in auth code flow
Co-authored-by: Kent Daleng <kent.daleng@nav.no>
This commit is contained in:
@@ -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()
|
||||
|
||||
1
go.mod
1
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
|
||||
|
||||
2
go.sum
2
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=
|
||||
|
||||
27
pkg/auth/validate.go
Normal file
27
pkg/auth/validate.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user