func middleAuth(server *Server) func(h echo.HandlerFunc) echo.HandlerFunc { return func(h echo.HandlerFunc) echo.HandlerFunc { return func(c *echo.Context) error { ah := c.Request().Header.Get(echo.Authorization) if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" { return errors.New("should be a bearer token") } val := ah[7:] if len(val) == 0 { return errors.New("bearer token is empty") } jwt, err := jose.ParseJWT(val) if err != nil { return errors.New("failed to parse token") } usr, err := getUserFromJWT(jwt) if err != nil { return err } err = server.oidc.VerifyJWT(jwt, usr.ClientID) if err != nil { return err } c.Set("User", usr) return h(c) } } }
// getClientIDFromAuthorizedRequest will extract the clientID from the bearer token. func getClientIDFromAuthorizedRequest(r *http.Request) (string, error) { rawToken, err := oidc.ExtractBearerToken(r) if err != nil { return "", err } jwt, err := jose.ParseJWT(rawToken) if err != nil { return "", err } claims, err := jwt.Claims() if err != nil { return "", err } sub, ok, err := claims.StringClaim("sub") if err != nil { return "", fmt.Errorf("failed to parse 'sub' claim: %v", err) } else if !ok || sub == "" { return "", errors.New("missing required 'sub' claim") } return sub, nil }
// Reads the OIDC JWT passed in the context and verifies it using the given OIDC client. // Returns the verified identity on success, error otherwise. func VerifiedIdentityFromContext(client *gooidc.Client, ctx context.Context) (*gooidc.Identity, error) { md, ok := metadata.FromContext(ctx) if !ok { return nil, errors.New("missing RPC credentials") } rawJWT, ok := md["jwt"] if !ok { return nil, errors.New("missing OIDC credentials") } if len(rawJWT) != 1 { return nil, errors.New("incorrect JWT data sent") } jwt, err := jose.ParseJWT(rawJWT[0]) if err != nil { return nil, err } if err := client.VerifyJWT(jwt); err != nil { return nil, err } claims, err := jwt.Claims() if err != nil { return nil, err } return gooidc.IdentityFromClaims(claims) }
func (r *idTokenRefresher) Refresh() (jose.JWT, error) { rt, ok := r.cfg[cfgRefreshToken] if !ok { return jose.JWT{}, errors.New("No valid id-token, and cannot refresh without refresh-token") } tokens, err := r.client.refreshToken(rt) if err != nil { return jose.JWT{}, fmt.Errorf("could not refresh token: %v", err) } jwt, err := jose.ParseJWT(tokens.IDToken) if err != nil { return jose.JWT{}, err } if tokens.RefreshToken != "" && tokens.RefreshToken != rt { r.cfg[cfgRefreshToken] = tokens.RefreshToken } r.cfg[cfgIDToken] = jwt.Encode() err = r.persister.Persist(r.cfg) if err != nil { return jose.JWT{}, fmt.Errorf("could not perist new tokens: %v", err) } return jwt, r.client.verifyJWT(jwt) }
// Parses and validates a JWT token, based on the client definition provided. func ValidateJWT(idToken string, client *oidc.Client) (jose.JWT, error) { jwt, err := jose.ParseJWT(idToken) if err != nil { return jose.JWT{}, err } return jwt, client.VerifyJWT(jwt) }
// AuthenticateToken decodes and verifies an ID Token using the OIDC client, if the verification succeeds, // then it will extract the user info from the JWT claims. func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, error) { jwt, err := jose.ParseJWT(value) if err != nil { return nil, false, err } client, err := a.client() if err != nil { return nil, false, err } if err := client.VerifyJWT(jwt); err != nil { return nil, false, err } claims, err := jwt.Claims() if err != nil { return nil, false, err } claim, ok, err := claims.StringClaim(a.usernameClaim) if err != nil { return nil, false, err } if !ok { return nil, false, fmt.Errorf("cannot find %q in JWT claims", a.usernameClaim) } var username string switch a.usernameClaim { case "email": // TODO(yifan): Check 'email_verified' to make sure the email is valid. username = claim default: // For all other cases, use issuerURL + claim as the user name. username = fmt.Sprintf("%s#%s", a.issuerURL, claim) } // TODO(yifan): Add UID, also populate the issuer to upper layer. info := &user.DefaultInfo{Name: username} if a.groupsClaim != "" { groups, found, err := claims.StringsClaim(a.groupsClaim) if err != nil { // Groups type is present but is not an array of strings, try to decode as a string. group, _, err := claims.StringClaim(a.groupsClaim) if err != nil { // Custom claim is present, but isn't an array of strings or a string. return nil, false, fmt.Errorf("custom group claim contains invalid type: %T", claims[a.groupsClaim]) } info.Groups = []string{group} } else if found { info.Groups = groups } } return info, true, nil }
func (s *grpcServer) parseGrpcMetadata(ctx context.Context, mustBeAdmin bool) (*GrpcClientInfo, error) { info := NewClientInfo() md, ok := metadata.FromContext(ctx) if !ok { return nil, fmt.Errorf("missing metadata") } //GET JWT var auth []string auth, ok = md["authorization"] if !ok || len(auth) == 0 { return nil, fmt.Errorf("missing authorization header") } ah := auth[0] if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" { return nil, errors.New("should be a bearer token") } val := ah[7:] if len(val) == 0 { return nil, errors.New("bearer token is empty") } jwt, err := jose.ParseJWT(val) if err != nil { return nil, err } info.JWT = jwt //APP ID var devices []string devices, ok = md["device"] if !ok || len(devices) == 0 { return nil, fmt.Errorf("missing 'device' header") } dbytes, err := base64.StdEncoding.DecodeString(devices[0]) if err != nil { return nil, err } device := &apipb.DeviceInfo{} err = device.Unmarshal(dbytes) if err != nil { return nil, err } info.Device = device //AUTH TOKEN id, email, err := s.authToken(jwt, mustBeAdmin) if err != nil { return nil, err } info.UserID = id info.Email = email info.State = GrpcClientInfoStateUnknown return info, nil }
func (p *oidcAuthProvider) idToken() (string, error) { p.mu.Lock() defer p.mu.Unlock() if idToken, ok := p.cfg[cfgIDToken]; ok && len(idToken) > 0 { valid, err := verifyJWTExpiry(p.now(), idToken) if err != nil { return "", err } if valid { // If the cached id token is still valid use it. return idToken, nil } } // Try to request a new token using the refresh token. rt, ok := p.cfg[cfgRefreshToken] if !ok || len(rt) == 0 { return "", errors.New("No valid id-token, and cannot refresh without refresh-token") } tokens, err := p.client.refreshToken(rt) if err != nil { return "", fmt.Errorf("could not refresh token: %v", err) } jwt, err := jose.ParseJWT(tokens.IDToken) if err != nil { return "", err } if err := p.client.verifyJWT(&jwt); err != nil { return "", err } // Create a new config to persist. newCfg := make(map[string]string) for key, val := range p.cfg { newCfg[key] = val } if tokens.RefreshToken != "" && tokens.RefreshToken != rt { newCfg[cfgRefreshToken] = tokens.RefreshToken } newCfg[cfgIDToken] = tokens.IDToken if err = p.persister.Persist(newCfg); err != nil { return "", fmt.Errorf("could not perist new tokens: %v", err) } // Update the in memory config to reflect the on disk one. p.cfg = newCfg return tokens.IDToken, nil }
func ParseTokenFromRequest(r *http.Request) (token jose.JWT, err error) { ah := r.Header.Get("Authorization") if ah == "" { err = errors.New("missing Authorization header") return } if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" { err = errors.New("should be a bearer token") return } return jose.ParseJWT(ah[7:]) }
// Authenticates a user based on a JWT token obtained directly from a provider (auth code flow, refresh flow). // Verifies the token claims, but doesn't require signature verification. // If the token is validated but the user for the username defined in the subject claim doesn't exist, // creates the user when autoRegister=true. func (auth *Authenticator) AuthenticateTrustedJWT(token string, provider *OIDCProvider, callbackURLFunc OIDCCallbackURLFunc) (User, jose.JWT, error) { // Parse JWT jwt, err := jose.ParseJWT(token) if err != nil { base.LogTo("OIDC+", "Error parsing JWT in AuthenticateTrustedJWT: %v", err) return nil, jose.JWT{}, err } // Verify claims - ensures that the token we received from the provider is valid for Sync Gateway if err := oidc.VerifyClaims(jwt, provider.Issuer, *provider.ClientID); err != nil { return nil, jose.JWT{}, err } return auth.authenticateJWT(jwt, provider) }
func (tkr *Tracker) validateJWT(jwtStr, infohash string) error { jwkSet := tkr.jwkSet if time.Now().After(jwkSet.validUntil) { return fmt.Errorf("Failed verify JWT due to stale JWK Set") } jwt, err := jose.ParseJWT(jwtStr) if err != nil { return err } validated, err := validateJWTSignature(&jwt, &jwkSet) if err != nil { return err } else if !validated { return errors.New("Failed to verify JWT with all available verifiers") } claims, err := jwt.Claims() if err != nil { return err } if claimedIssuer, ok, err := claims.StringClaim("iss"); claimedIssuer != jwkSet.Issuer || err != nil || !ok { return errors.New("Failed to validate JWT issuer claim") } if claimedAudience, ok, err := claims.StringClaim("aud"); claimedAudience != tkr.Config.JWTAudience || err != nil || !ok { return errors.New("Failed to validate JWT audience claim") } claimedInfohash, ok, err := claims.StringClaim("infohash") if err != nil || !ok { return errors.New("Failed to validate JWT infohash claim") } unescapedInfohash, err := url.QueryUnescape(claimedInfohash) if err != nil { return errors.New("Failed to unescape JWT infohash claim") } if unescapedInfohash != infohash { return errors.New("Failed to match infohash claim with requested infohash") } return nil }
// RefreshToken uses a refresh token to exchange for a new OIDC JWT ID Token. func (c *Client) RefreshToken(refreshToken string) (jose.JWT, error) { oac, err := c.OAuthClient() if err != nil { return jose.JWT{}, err } t, err := oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken) if err != nil { return jose.JWT{}, err } jwt, err := jose.ParseJWT(t.IDToken) if err != nil { return jose.JWT{}, err } return jwt, c.VerifyJWT(jwt) }
// Exchange an OAuth2 auth code for an OIDC JWT func (c *Client) ExchangeAuthCode(code string) (jose.JWT, error) { oac, err := c.OAuthClient() if err != nil { return jose.JWT{}, err } t, err := oac.Exchange(code) if err != nil { return jose.JWT{}, err } jwt, err := jose.ParseJWT(t.IDToken) if err != nil { return jose.JWT{}, err } return jwt, c.VerifyJWT(jwt) }
func (l *TokenValidator) ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) { log.Info("validating") rawToken, err := oidc.ExtractBearerToken(r) if err != nil { log.Error("token.go: failed to get jwt from header") writeError(rw, http.StatusUnauthorized, "missing or invalid token") return } jwt, err := jose.ParseJWT(rawToken) if err != nil { log.Error("token.go: failed to parse jwt") writeError(rw, http.StatusUnauthorized, "missing or invalid token") return } err = l.accounts.Oidc.VerifyJWT(jwt) if err != nil { log.Errorf("token.go: Failed to verify signature: %v", err) writeError(rw, http.StatusUnauthorized, "invalid token") } claims, err := jwt.Claims() if err != nil { log.Error("token.go: failed to get claims", err) writeError(rw, http.StatusUnauthorized, "missing or invalid token") return } sub, ok, err := claims.StringClaim("sub") if err != nil { log.Errorf("token.go: failed to parse 'sub' claim: %v", err) writeError(rw, http.StatusUnauthorized, "missing or invalid token") return } if !ok || sub == "" { log.Error("token.go: missing required 'sub' claim") writeError(rw, http.StatusUnauthorized, "missing or invalid token") return } fmt.Println("token.go: verified token for", sub) r.Header.Set("sub", sub) next(rw, r) }
func handleCallbackFunc(c *oidc.Client, claims *jose.Claims, refresh *string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { phttp.WriteError(w, http.StatusBadRequest, "code query param must be set") return } oac, err := c.OAuthClient() if err != nil { phttp.WriteError(w, http.StatusInternalServerError, fmt.Sprintf("unable to create oauth client: %v", err)) return } t, err := oac.RequestToken(oauth2.GrantTypeAuthCode, code) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify auth code with issuer: %v", err)) return } // Get id token and claims. tok, err := jose.ParseJWT(t.IDToken) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to parse id_token: %v", err)) return } if err := c.VerifyJWT(tok); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify the JWT: %v", err)) return } if *claims, err = tok.Claims(); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to construct claims: %v", err)) return } // Get refresh token. *refresh = t.RefreshToken w.WriteHeader(http.StatusOK) } }
// Returns TokenClaims if and only if // - the given token string is an appropriately formatted JWT // - the JWT contains nonempty "aud" and "sub" claims // - the JWT can be verified for the client associated with the "aud" claim // using the given keys func parseAndVerifyTokenClaims(token string, issuer url.URL, keys []key.PublicKey) (TokenClaims, error) { jwt, err := jose.ParseJWT(token) if err != nil { return TokenClaims{}, err } claims, err := jwt.Claims() if err != nil { return TokenClaims{}, err } clientID, ok, err := claims.StringClaim("aud") if err != nil { return TokenClaims{}, err } if !ok || clientID == "" { return TokenClaims{}, errors.New("no aud(client ID) claim") } sub, ok, err := claims.StringClaim("sub") if err != nil { return TokenClaims{}, err } if !ok || sub == "" { return TokenClaims{}, errors.New("no sub claim") } noop := func() error { return nil } keysFunc := func() []key.PublicKey { return keys } verifier := oidc.NewJWTVerifier(issuer.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { return TokenClaims{}, err } timeClaimsToInt(claims) return TokenClaims{claims}, nil }
func handleCallbackFunc(c *oidc.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { code := r.URL.Query().Get("code") if code == "" { phttp.WriteError(w, http.StatusBadRequest, "code query param must be set") return } tokens, err := exchangeAuthCode(c, code) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify auth code with issuer: %v", err)) return } tok, err := jose.ParseJWT(tokens.IDToken) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to parse JWT: %v", err)) return } claims, err := tok.Claims() if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to construct claims: %v", err)) return } s := fmt.Sprintf(` <html> <body> <p> Token: %v</p> <p> Claims: %v </p> <a href="/resend?jwt=%s">Resend Verification Email</a> <p> Refresh Token: %v </p> </body> </html>`, tok.Encode(), claims, tok.Encode(), tokens.RefreshToken) w.Write([]byte(s)) } }
func (c *Client) ClientCredsToken(scope []string) (jose.JWT, error) { if !c.providerConfig.SupportsGrantType(oauth2.GrantTypeClientCreds) { return jose.JWT{}, fmt.Errorf("%v grant type is not supported", oauth2.GrantTypeClientCreds) } oac, err := c.OAuthClient() if err != nil { return jose.JWT{}, err } t, err := oac.ClientCredsToken(scope) if err != nil { return jose.JWT{}, err } jwt, err := jose.ParseJWT(t.IDToken) if err != nil { return jose.JWT{}, err } return jwt, c.VerifyJWT(jwt) }
func verifyJWTExpiry(now time.Time, s string) (valid bool, err error) { jwt, err := jose.ParseJWT(s) if err != nil { return false, fmt.Errorf("invalid %q", cfgIDToken) } claims, err := jwt.Claims() if err != nil { return false, err } exp, ok, err := claims.TimeClaim("exp") switch { case err != nil: return false, fmt.Errorf("failed to parse 'exp' claim: %v", err) case !ok: return false, errors.New("missing required 'exp' claim") case exp.After(now.Add(expiryDelta)): return true, nil } return false, nil }
func getJWTToken(ctx context.Context) (jose.JWT, error) { md, ok := metadata.FromContext(ctx) if !ok { return jose.JWT{}, fmt.Errorf("missing metadata") } var auth []string auth, ok = md["Authorization"] if !ok || len(auth) == 0 { return jose.JWT{}, fmt.Errorf("missing authorization header") } if len(auth) > 1 { return jose.JWT{}, fmt.Errorf("too many authorization header") } ah := auth[0] if len(ah) <= 6 || strings.ToUpper(ah[0:6]) != "BEARER" { return jose.JWT{}, errors.New("should be a bearer token") } val := ah[7:] if len(val) == 0 { return jose.JWT{}, errors.New("bearer token is empty") } return jose.ParseJWT(val) }
// Authenticates a user based on a JWT token string and a set of providers. Attempts to match the // issuer in the token with a provider. // Used to authenticate a JWT token coming from an insecure source (e.g. client request) // If the token is validated but the user for the username defined in the subject claim doesn't exist, // creates the user when autoRegister=true. func (auth *Authenticator) AuthenticateUntrustedJWT(token string, providers OIDCProviderMap, callbackURLFunc OIDCCallbackURLFunc) (User, jose.JWT, error) { base.LogTo("OIDC+", "AuthenticateJWT called with token: %s", token) // Parse JWT (needed to determine issuer/provider) jwt, err := jose.ParseJWT(token) if err != nil { base.LogTo("OIDC+", "Error parsing JWT in AuthenticateJWT: %v", err) return nil, jose.JWT{}, err } // Get client for issuer issuer, audiences, err := GetJWTIssuer(jwt) base.LogTo("OIDC+", "JWT issuer: %v, audiences: %v", issuer, audiences) if err != nil { base.LogTo("OIDC+", "Error getting JWT issuer: %v", err) return nil, jose.JWT{}, err } base.LogTo("OIDC+", "Call GetProviderForIssuer w/ providers: %+v", providers) provider := providers.GetProviderForIssuer(issuer, audiences) base.LogTo("OIDC+", "Provider for issuer: %+v", provider) if provider == nil { return nil, jose.JWT{}, fmt.Errorf("No provider found for issuer %v", issuer) } // VerifyJWT validates the claims and signature on the JWT client := provider.GetClient(callbackURLFunc) err = client.VerifyJWT(jwt) if err != nil { base.LogTo("OIDC+", "Client %v could not verify JWT. Error: %v", client, err) return nil, jwt, err } return auth.authenticateJWT(jwt, provider) }
// AuthenticateToken decodes and verifies a JWT using the OIDC client, if the verification succeeds, // then it will extract the user info from the JWT claims. func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, error) { jwt, err := jose.ParseJWT(value) if err != nil { return nil, false, err } if err := a.client.VerifyJWT(jwt); err != nil { return nil, false, err } claims, err := jwt.Claims() if err != nil { return nil, false, err } claim, ok, err := claims.StringClaim(a.usernameClaim) if err != nil { return nil, false, err } if !ok { return nil, false, fmt.Errorf("cannot find %q in JWT claims", a.usernameClaim) } var username string switch a.usernameClaim { case "email": // TODO(yifan): Check 'email_verified' to make sure the email is valid. username = claim default: // For all other cases, use issuerURL + claim as the user name. username = fmt.Sprintf("%s#%s", a.clientConfig.ProviderConfig.Issuer, claim) } // TODO(yifan): Add UID and Group, also populate the issuer to upper layer. return &user.DefaultInfo{Name: username}, true, nil }
// ParseAndVerifyPasswordResetToken parses a string into a an PasswordReset, verifies the signature, and ensures that required claims are present. // In addition to the usual claims required by the OIDC spec, "aud" and "sub" must be present as well as ClaimPasswordResetCallback, ClaimPasswordResetEmail and ClaimPasswordResetPassword. func ParseAndVerifyPasswordResetToken(token string, issuer url.URL, keys []key.PublicKey) (PasswordReset, error) { jwt, err := jose.ParseJWT(token) if err != nil { return PasswordReset{}, err } claims, err := jwt.Claims() if err != nil { return PasswordReset{}, err } cb, ok, err := claims.StringClaim(ClaimPasswordResetCallback) if err != nil { return PasswordReset{}, err } var clientID string if ok && cb != "" { clientID, ok, err = claims.StringClaim("aud") if err != nil { return PasswordReset{}, err } if !ok || clientID == "" { return PasswordReset{}, errors.New("no aud(client ID) claim") } } if _, err := url.Parse(cb); err != nil { return PasswordReset{}, fmt.Errorf("callback URL not parseable: %v", cb) } pw, ok, err := claims.StringClaim(ClaimPasswordResetPassword) if err != nil { return PasswordReset{}, err } if pw == "" { return PasswordReset{}, fmt.Errorf("no %q claim", ClaimPasswordResetPassword) } sub, ok, err := claims.StringClaim("sub") if err != nil { return PasswordReset{}, err } if sub == "" { return PasswordReset{}, errors.New("no sub claim") } noop := func() error { return nil } keysFunc := func() []key.PublicKey { return keys } verifier := oidc.NewJWTVerifier(issuer.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { return PasswordReset{}, err } return PasswordReset{ claims: claims, }, nil }
// ParseAndVerifyEmailVerificationToken parses a string into a an EmailVerification, verifies the signature, and ensures that required claims are present. // In addition to the usual claims required by the OIDC spec, "aud" and "sub" must be present as well as ClaimEmailVerificationCallback and ClaimEmailVerificationEmail. func ParseAndVerifyEmailVerificationToken(token string, issuer url.URL, keys []key.PublicKey) (EmailVerification, error) { jwt, err := jose.ParseJWT(token) if err != nil { return EmailVerification{}, err } claims, err := jwt.Claims() if err != nil { return EmailVerification{}, err } clientID, ok, err := claims.StringClaim("aud") if err != nil { return EmailVerification{}, err } if !ok { return EmailVerification{}, errors.New("no aud(client ID) claim") } cb, ok, err := claims.StringClaim(ClaimEmailVerificationCallback) if err != nil { return EmailVerification{}, err } if cb == "" { return EmailVerification{}, fmt.Errorf("no %q claim", ClaimEmailVerificationCallback) } if _, err := url.Parse(cb); err != nil { return EmailVerification{}, fmt.Errorf("callback URL not parseable: %v", cb) } email, ok, err := claims.StringClaim(ClaimEmailVerificationEmail) if err != nil { return EmailVerification{}, err } if email == "" { return EmailVerification{}, fmt.Errorf("no %q claim", ClaimEmailVerificationEmail) } sub, ok, err := claims.StringClaim("sub") if err != nil { return EmailVerification{}, err } if sub == "" { return EmailVerification{}, errors.New("no sub claim") } noop := func() error { return nil } keysFunc := func() []key.PublicKey { return keys } verifier := oidc.NewJWTVerifier(issuer.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { return EmailVerification{}, err } return EmailVerification{ claims: claims, }, nil }
func (s *UserMgmtServer) getCreds(r *http.Request) (api.Creds, error) { token, err := oidc.ExtractBearerToken(r) if err != nil { log.Errorf("userMgmtServer: GetCreds err: %q", err) return api.Creds{}, api.ErrorUnauthorized } jwt, err := jose.ParseJWT(token) if err != nil { log.Errorf("userMgmtServer: GetCreds err: %q", err) return api.Creds{}, api.ErrorUnauthorized } claims, err := jwt.Claims() if err != nil { log.Errorf("userMgmtServer: GetCreds err: %q", err) return api.Creds{}, api.ErrorUnauthorized } clientID, ok, err := claims.StringClaim("aud") if err != nil { log.Errorf("userMgmtServer: GetCreds err: %q", err) return api.Creds{}, err } if !ok || clientID == "" { return api.Creds{}, errors.New("no aud(client ID) claim") } verifier := s.jwtvFactory(clientID) if err := verifier.Verify(jwt); err != nil { log.Errorf("userMgmtServer: GetCreds err: %q", err) return api.Creds{}, api.ErrorUnauthorized } sub, ok, err := claims.StringClaim("sub") if err != nil { log.Errorf("userMgmtServer: GetCreds err: %q", err) return api.Creds{}, err } if !ok || sub == "" { return api.Creds{}, api.ErrorUnauthorized } usr, err := s.um.Get(sub) if err != nil { if err == user.ErrorNotFound { return api.Creds{}, api.ErrorUnauthorized } log.Errorf("userMgmtServer: GetCreds err: %q", err) return api.Creds{}, err } isAdmin, err := s.cir.IsDexAdmin(clientID) if err != nil { log.Errorf("userMgmtServer: GetCreds err: %q", err) return api.Creds{}, err } if !isAdmin { return api.Creds{}, api.ErrorForbidden } return api.Creds{ ClientID: clientID, User: usr, }, nil }
func newOIDCAuthProvider(_ string, cfg map[string]string, persister rest.AuthProviderConfigPersister) (rest.AuthProvider, error) { issuer := cfg[cfgIssuerUrl] if issuer == "" { return nil, fmt.Errorf("Must provide %s", cfgIssuerUrl) } clientID := cfg[cfgClientID] if clientID == "" { return nil, fmt.Errorf("Must provide %s", cfgClientID) } clientSecret := cfg[cfgClientSecret] if clientSecret == "" { return nil, fmt.Errorf("Must provide %s", cfgClientSecret) } var certAuthData []byte var err error if cfg[cfgCertificateAuthorityData] != "" { certAuthData, err = base64.StdEncoding.DecodeString(cfg[cfgCertificateAuthorityData]) if err != nil { return nil, err } } clientConfig := rest.Config{ TLSClientConfig: rest.TLSClientConfig{ CAFile: cfg[cfgCertificateAuthority], CAData: certAuthData, }, } trans, err := rest.TransportFor(&clientConfig) if err != nil { return nil, err } hc := &http.Client{Transport: trans} providerCfg, err := oidc.FetchProviderConfig(hc, issuer) if err != nil { return nil, fmt.Errorf("error fetching provider config: %v", err) } scopes := strings.Split(cfg[cfgExtraScopes], ",") oidcCfg := oidc.ClientConfig{ HTTPClient: hc, Credentials: oidc.ClientCredentials{ ID: clientID, Secret: clientSecret, }, ProviderConfig: providerCfg, Scope: append(scopes, oidc.DefaultScope...), } client, err := oidc.NewClient(oidcCfg) if err != nil { return nil, fmt.Errorf("error creating OIDC Client: %v", err) } oClient := &oidcClient{client} var initialIDToken jose.JWT if cfg[cfgIDToken] != "" { initialIDToken, err = jose.ParseJWT(cfg[cfgIDToken]) if err != nil { return nil, err } } return &oidcAuthProvider{ initialIDToken: initialIDToken, refresher: &idTokenRefresher{ client: oClient, cfg: cfg, persister: persister, }, }, nil }
// handleVerifyEmailResendFunc will resend an email-verification email given a valid JWT for the user and a redirect URL. // This handler is meant to be wrapped in clientTokenMiddleware, so a valid // bearer token for the client is expected to be present. // The user's JWT should be in the "token" parameter and the redirect URL should // be in the "redirect_uri" param. Note that this re func handleVerifyEmailResendFunc( issuerURL url.URL, srvKeysFunc func() ([]key.PublicKey, error), emailer *useremail.UserEmailer, userRepo user.UserRepo, clientIdentityRepo client.ClientIdentityRepo) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var params struct { Token string `json:"token"` RedirectURI string `json:"redirectURI"` } err := decoder.Decode(¶ms) if err != nil { writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "unable to parse body as JSON")) return } token := params.Token if token == "" { writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "missing valid JWT")) return } clientID, err := getClientIDFromAuthorizedRequest(r) if err != nil { log.Errorf("Failed to extract clientID: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorInvalidRequest, "cilent could not be extracted from bearer token.")) return } cm, err := clientIdentityRepo.Metadata(clientID) if err == client.ErrorNotFound { log.Errorf("No such client: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid client_id")) return } if err != nil { log.Errorf("Error getting ClientMetadata: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } noop := func() error { return nil } keysFunc := func() []key.PublicKey { keys, err := srvKeysFunc() if err != nil { log.Errorf("Error getting keys: %v", err) } return keys } jwt, err := jose.ParseJWT(token) if err != nil { log.Errorf("Failed to Parse JWT: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "token could not be parsed")) return } verifier := oidc.NewJWTVerifier(issuerURL.String(), clientID, noop, keysFunc) if err := verifier.Verify(jwt); err != nil { log.Errorf("Failed to Verify JWT: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorAccessDenied, "invalid token could not be verified")) return } claims, err := jwt.Claims() if err != nil { log.Errorf("Failed to extract claims from JWT: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid token could not be parsed")) return } sub, ok, err := claims.StringClaim("sub") if err != nil || !ok || sub == "" { log.Errorf("Failed to extract sub claim from JWT: err:%q ok:%v", err, ok) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "could not extract sub claim from token")) return } usr, err := userRepo.Get(nil, sub) if err != nil { if err == user.ErrorNotFound { log.Errorf("Failed to find user specified by token: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "could not find user")) return } log.Errorf("Failed to fetch user: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } if usr.EmailVerified { log.Errorf("User's email already verified") writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "email already verified")) return } aud, _, _ := claims.StringClaim("aud") if aud != clientID { log.Errorf("aud of token and sub of bearer token must match: %v", err) writeAPIError(w, http.StatusForbidden, newAPIError(errorAccessDenied, "JWT is from another client.")) return } redirectURLStr := params.RedirectURI if redirectURLStr == "" { log.Errorf("No redirect URL: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "must provide a redirect_uri")) return } redirectURL, err := url.Parse(redirectURLStr) if err != nil { log.Errorf("Unparsable URL: %v", err) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return } *redirectURL, err = client.ValidRedirectURL(redirectURL, cm.RedirectURLs) if err != nil { switch err { case (client.ErrorInvalidRedirectURL): log.Errorf("Request provided unregistered redirect URL: %s", redirectURLStr) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return case (client.ErrorNoValidRedirectURLs): log.Errorf("There are no registered URLs for the requested client: %s", redirectURL) writeAPIError(w, http.StatusBadRequest, newAPIError(errorInvalidRequest, "invalid redirect_uri")) return } } _, err = emailer.SendEmailVerification(usr.ID, clientID, *redirectURL) if err != nil { log.Errorf("Failed to send email verification email: %v", err) writeAPIError(w, http.StatusInternalServerError, newAPIError(errorServerError, "could not send email at this time")) return } writeResponseWithBody(w, http.StatusOK, struct{}{}) } }
// AuthenticateToken decodes and verifies an ID Token using the OIDC client, if the verification succeeds, // then it will extract the user info from the JWT claims. func (a *OIDCAuthenticator) AuthenticateToken(value string) (user.Info, bool, error) { jwt, err := jose.ParseJWT(value) if err != nil { return nil, false, err } client, err := a.client() if err != nil { return nil, false, err } if err := client.VerifyJWT(jwt); err != nil { return nil, false, err } claims, err := jwt.Claims() if err != nil { return nil, false, err } claim, ok, err := claims.StringClaim(a.usernameClaim) if err != nil { return nil, false, err } if !ok { return nil, false, fmt.Errorf("cannot find %q in JWT claims", a.usernameClaim) } var username string switch a.usernameClaim { case "email": verified, ok := claims["email_verified"] if !ok { return nil, false, errors.New("'email_verified' claim not present") } emailVerified, ok := verified.(bool) if !ok { // OpenID Connect spec defines 'email_verified' as a boolean. For now, be a pain and error if // it's a different type. If there are enough misbehaving providers we can relax this latter. // // See: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims return nil, false, fmt.Errorf("malformed claim 'email_verified', expected boolean got %T", verified) } if !emailVerified { return nil, false, errors.New("email not verified") } username = claim default: // For all other cases, use issuerURL + claim as the user name. username = fmt.Sprintf("%s#%s", a.issuerURL, claim) } // TODO(yifan): Add UID, also populate the issuer to upper layer. info := &user.DefaultInfo{Name: username} if a.groupsClaim != "" { groups, found, err := claims.StringsClaim(a.groupsClaim) if err != nil { // Groups type is present but is not an array of strings, try to decode as a string. group, _, err := claims.StringClaim(a.groupsClaim) if err != nil { // Custom claim is present, but isn't an array of strings or a string. return nil, false, fmt.Errorf("custom group claim contains invalid type: %T", claims[a.groupsClaim]) } info.Groups = []string{group} } else if found { info.Groups = groups } } return info, true, nil }
func (c *clientTokenMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { respondError := func() { writeAPIError(w, http.StatusUnauthorized, newAPIError(errorAccessDenied, "missing or invalid token")) } if c.keysFunc == nil { log.Errorf("Misconfigured clientTokenMiddleware, keysFunc is not set") respondError() return } if c.ciRepo == nil { log.Errorf("Misconfigured clientTokenMiddleware, ClientIdentityRepo is not set") respondError() return } rawToken, err := oidc.ExtractBearerToken(r) if err != nil { log.Errorf("Failed to extract token from request: %v", err) respondError() return } jwt, err := jose.ParseJWT(rawToken) if err != nil { log.Errorf("Failed to parse JWT from token: %v", err) respondError() return } keys, err := c.keysFunc() if err != nil { log.Errorf("Failed to get keys: %v", err) writeAPIError(w, http.StatusUnauthorized, newAPIError(errorAccessDenied, "")) respondError() return } else if len(keys) == 0 { log.Error("No keys available for verification in client token middleware") writeAPIError(w, http.StatusUnauthorized, newAPIError(errorAccessDenied, "")) respondError() return } ok, err := oidc.VerifySignature(jwt, keys) if err != nil { log.Errorf("Failed to verify signature: %v", err) respondError() return } else if !ok { log.Info("Invalid token") respondError() return } clientID, err := oidc.VerifyClientClaims(jwt, c.issuerURL) if err != nil { log.Errorf("Failed to verify JWT claims: %v", err) respondError() return } md, err := c.ciRepo.Metadata(clientID) if md == nil || err != nil { log.Errorf("Failed to find clientID: %s, error=%v", clientID, err) respondError() return } log.Infof("Authenticated token for client ID %s", clientID) c.next.ServeHTTP(w, r) }
func handleCallbackFunc(c *oidc.Client) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { refreshToken := r.URL.Query().Get("refresh_token") code := r.URL.Query().Get("code") oac, err := c.OAuthClient() if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to create OAuth2 client: %v", err)) return } var token oauth2.TokenResponse switch { case code != "": if token, err = oac.RequestToken(oauth2.GrantTypeAuthCode, code); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to verify auth code with issuer: %v", err)) return } case refreshToken != "": if token, err = oac.RequestToken(oauth2.GrantTypeRefreshToken, refreshToken); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to refresh token: %v", err)) return } if token.RefreshToken == "" { token.RefreshToken = refreshToken } default: phttp.WriteError(w, http.StatusBadRequest, "code query param must be set") return } tok, err := jose.ParseJWT(token.IDToken) if err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to parse JWT: %v", err)) return } claims := new(bytes.Buffer) if err := json.Indent(claims, tok.Payload, "", " "); err != nil { phttp.WriteError(w, http.StatusBadRequest, fmt.Sprintf("unable to construct claims: %v", err)) return } s := fmt.Sprintf(` <html> <head> <style> /* make pre wrap */ pre { white-space: pre-wrap; /* css-3 */ white-space: -moz-pre-wrap; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */ word-wrap: break-word; /* Internet Explorer 5.5+ */ } </style> </head> <body> <p> Token: <pre><code>%v</code></pre></p> <p> Claims: <pre><code>%v</code></pre></p> <p> Refresh Token: <pre><code>%v</code></pre></p> <p><a href="%s?refresh_token=%s">Redeem refresh token</a><p> <p><a href="/resend?jwt=%s">Resend Verification Email</a></p> </body> </html>`, tok.Encode(), claims.String(), token.RefreshToken, r.URL.Path, token.RefreshToken, tok.Encode()) w.Write([]byte(s)) } }