Example #1
0
func TestOIDCDiscoveryNoKeyEndpoint(t *testing.T) {
	var err error
	expectErr := fmt.Errorf("failed to fetch provider config after 0 retries")

	cert := path.Join(os.TempDir(), "oidc-cert")
	key := path.Join(os.TempDir(), "oidc-key")

	defer os.Remove(cert)
	defer os.Remove(key)

	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)

	op := oidctesting.NewOIDCProvider(t)
	srv, err := op.ServeTLSWithKeyPair(cert, key)
	if err != nil {
		t.Fatalf("Cannot start server %v", err)
	}
	defer srv.Close()

	op.PCFG = oidc.ProviderConfig{
		Issuer: oidctesting.MustParseURL(srv.URL), // An invalid ProviderConfig. Keys endpoint is required.
	}

	_, err = New(OIDCOptions{srv.URL, "client-foo", cert, "sub", "", 0, 0})
	if !reflect.DeepEqual(err, expectErr) {
		t.Errorf("Expecting %v, but got %v", expectErr, err)
	}
}
Example #2
0
func TestOIDCDiscoverySecureConnection(t *testing.T) {
	// Verify that plain HTTP issuer URL is forbidden.
	op := oidctesting.NewOIDCProvider(t)
	srv := httptest.NewServer(op.Mux)
	defer srv.Close()

	op.PCFG = oidc.ProviderConfig{
		Issuer:       oidctesting.MustParseURL(srv.URL),
		KeysEndpoint: oidctesting.MustParseURL(srv.URL + "/keys"),
	}

	expectErr := fmt.Errorf("'oidc-issuer-url' (%q) has invalid scheme (%q), require 'https'", srv.URL, "http")

	_, err := New(OIDCOptions{srv.URL, "client-foo", "", "sub", "", 0, 0})
	if !reflect.DeepEqual(err, expectErr) {
		t.Errorf("Expecting %v, but got %v", expectErr, err)
	}

	// Verify the cert/key pair works.
	cert1 := path.Join(os.TempDir(), "oidc-cert-1")
	key1 := path.Join(os.TempDir(), "oidc-key-1")
	cert2 := path.Join(os.TempDir(), "oidc-cert-2")
	key2 := path.Join(os.TempDir(), "oidc-key-2")

	defer os.Remove(cert1)
	defer os.Remove(key1)
	defer os.Remove(cert2)
	defer os.Remove(key2)

	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert1, key1)
	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert2, key2)

	// Create a TLS server using cert/key pair 1.
	tlsSrv, err := op.ServeTLSWithKeyPair(cert1, key1)
	if err != nil {
		t.Fatalf("Cannot start server: %v", err)
	}
	defer tlsSrv.Close()

	op.PCFG = oidc.ProviderConfig{
		Issuer:       oidctesting.MustParseURL(tlsSrv.URL),
		KeysEndpoint: oidctesting.MustParseURL(tlsSrv.URL + "/keys"),
	}

	// Create a client using cert2, should fail.
	_, err = New(OIDCOptions{tlsSrv.URL, "client-foo", cert2, "sub", "", 0, 0})
	if err == nil {
		t.Fatalf("Expecting error, but got nothing")
	}

}
Example #3
0
func TestNewOIDCAuthProvider(t *testing.T) {
	tempDir, err := ioutil.TempDir(os.TempDir(), "oidc_test")
	if err != nil {
		t.Fatalf("Cannot make temp dir %v", err)
	}
	cert := path.Join(tempDir, "oidc-cert")
	key := path.Join(tempDir, "oidc-key")
	defer os.RemoveAll(tempDir)

	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)
	op := oidctesting.NewOIDCProvider(t, "")
	srv, err := op.ServeTLSWithKeyPair(cert, key)
	if err != nil {
		t.Fatalf("Cannot start server %v", err)
	}
	defer srv.Close()

	certData, err := ioutil.ReadFile(cert)
	if err != nil {
		t.Fatalf("Could not read cert bytes %v", err)
	}

	makeToken := func(exp time.Time) *jose.JWT {
		jwt, err := jose.NewSignedJWT(jose.Claims(map[string]interface{}{
			"exp": exp.UTC().Unix(),
		}), op.PrivKey.Signer())
		if err != nil {
			t.Fatalf("Could not create signed JWT %v", err)
		}
		return jwt
	}

	t0 := time.Now()

	goodToken := makeToken(t0.Add(time.Hour)).Encode()
	expiredToken := makeToken(t0.Add(-time.Hour)).Encode()

	tests := []struct {
		name string

		cfg         map[string]string
		wantInitErr bool

		client       OIDCClient
		wantCfg      map[string]string
		wantTokenErr bool
	}{
		{
			// A Valid configuration
			name: "no id token and no refresh token",
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
			},
			wantTokenErr: true,
		},
		{
			name: "valid config with an initial token",
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
				cfgIDToken:              goodToken,
			},
			client: new(noRefreshOIDCClient),
			wantCfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
				cfgIDToken:              goodToken,
			},
		},
		{
			name: "invalid ID token with a refresh token",
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
				cfgRefreshToken:         "foo",
				cfgIDToken:              expiredToken,
			},
			client: &mockOIDCClient{
				tokenResponse: oauth2.TokenResponse{
					IDToken: goodToken,
				},
			},
			wantCfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
				cfgRefreshToken:         "foo",
				cfgIDToken:              goodToken,
			},
		},
		{
			name: "invalid ID token with a refresh token, server returns new refresh token",
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
				cfgRefreshToken:         "foo",
				cfgIDToken:              expiredToken,
			},
			client: &mockOIDCClient{
				tokenResponse: oauth2.TokenResponse{
					IDToken:      goodToken,
					RefreshToken: "bar",
				},
			},
			wantCfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
				cfgRefreshToken:         "bar",
				cfgIDToken:              goodToken,
			},
		},
		{
			name: "expired token and no refresh otken",
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
				cfgIDToken:              expiredToken,
			},
			wantTokenErr: true,
		},
		{
			name: "valid base64d ca",
			cfg: map[string]string{
				cfgIssuerUrl:                srv.URL,
				cfgCertificateAuthorityData: base64.StdEncoding.EncodeToString(certData),
				cfgClientID:                 "client-id",
				cfgClientSecret:             "client-secret",
			},
			client:       new(noRefreshOIDCClient),
			wantTokenErr: true,
		},
		{
			name: "missing client ID",
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientSecret:         "client-secret",
			},
			wantInitErr: true,
		},
		{
			name: "missing client secret",
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
			},
			wantInitErr: true,
		},
		{
			name: "missing issuer URL",
			cfg: map[string]string{
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "secret",
			},
			wantInitErr: true,
		},
		{
			name: "missing TLS config",
			cfg: map[string]string{
				cfgIssuerUrl:    srv.URL,
				cfgClientID:     "client-id",
				cfgClientSecret: "secret",
			},
			wantInitErr: true,
		},
	}

	for _, tt := range tests {
		clearCache()

		p, err := newOIDCAuthProvider("cluster.example.com", tt.cfg, new(persister))
		if tt.wantInitErr {
			if err == nil {
				t.Errorf("%s: want non-nil err", tt.name)
			}
			continue
		}

		if err != nil {
			t.Errorf("%s: unexpected error on newOIDCAuthProvider: %v", tt.name, err)
			continue
		}

		provider := p.(*oidcAuthProvider)
		provider.client = tt.client
		provider.now = func() time.Time { return t0 }

		if _, err := provider.idToken(); err != nil {
			if !tt.wantTokenErr {
				t.Errorf("%s: failed to get id token: %v", tt.name, err)
			}
			continue
		}
		if tt.wantTokenErr {
			t.Errorf("%s: expected to not get id token: %v", tt.name, err)
			continue
		}

		if !reflect.DeepEqual(tt.wantCfg, provider.cfg) {
			t.Errorf("%s: expected config %#v got %#v", tt.name, tt.wantCfg, provider.cfg)
		}
	}
}
Example #4
0
func TestNewOIDCAuthProvider(t *testing.T) {
	tempDir, err := ioutil.TempDir(os.TempDir(), "oidc_test")
	if err != nil {
		t.Fatalf("Cannot make temp dir %v", err)
	}
	cert := path.Join(tempDir, "oidc-cert")
	key := path.Join(tempDir, "oidc-key")

	defer os.Remove(cert)
	defer os.Remove(key)
	defer os.Remove(tempDir)

	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)
	op := oidctesting.NewOIDCProvider(t)
	srv, err := op.ServeTLSWithKeyPair(cert, key)
	if err != nil {
		t.Fatalf("Cannot start server %v", err)
	}
	defer srv.Close()
	op.AddMinimalProviderConfig(srv)

	certData, err := ioutil.ReadFile(cert)
	if err != nil {
		t.Fatalf("Could not read cert bytes %v", err)
	}

	jwt, err := jose.NewSignedJWT(jose.Claims(map[string]interface{}{
		"test": "jwt",
	}), op.PrivKey.Signer())
	if err != nil {
		t.Fatalf("Could not create signed JWT %v", err)
	}

	tests := []struct {
		cfg map[string]string

		wantErr            bool
		wantInitialIDToken jose.JWT
	}{
		{
			// A Valid configuration
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
			},
		},
		{
			// A Valid configuration with an Initial JWT
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "client-secret",
				cfgIDToken:              jwt.Encode(),
			},
			wantInitialIDToken: *jwt,
		},
		{
			// Valid config, but using cfgCertificateAuthorityData
			cfg: map[string]string{
				cfgIssuerUrl:                srv.URL,
				cfgCertificateAuthorityData: base64.StdEncoding.EncodeToString(certData),
				cfgClientID:                 "client-id",
				cfgClientSecret:             "client-secret",
			},
		},
		{
			// Missing client id
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientSecret:         "client-secret",
			},
			wantErr: true,
		},
		{
			// Missing client secret
			cfg: map[string]string{
				cfgIssuerUrl:            srv.URL,
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
			},
			wantErr: true,
		},
		{
			// Missing issuer url.
			cfg: map[string]string{
				cfgCertificateAuthority: cert,
				cfgClientID:             "client-id",
				cfgClientSecret:         "secret",
			},
			wantErr: true,
		},
		{
			// No TLS config
			cfg: map[string]string{
				cfgIssuerUrl:    srv.URL,
				cfgClientID:     "client-id",
				cfgClientSecret: "secret",
			},
			wantErr: true,
		},
	}

	for i, tt := range tests {
		ap, err := newOIDCAuthProvider("cluster.example.com", tt.cfg, nil)
		if tt.wantErr {
			if err == nil {
				t.Errorf("case %d: want non-nil err", i)
			}
			continue
		}

		if err != nil {
			t.Errorf("case %d: unexpected error on newOIDCAuthProvider: %v", i, err)
			continue
		}

		oidcAP, ok := ap.(*oidcAuthProvider)
		if !ok {
			t.Errorf("case %d: expected ap to be an oidcAuthProvider", i)
			continue
		}

		if diff := compareJWTs(tt.wantInitialIDToken, oidcAP.initialIDToken); diff != "" {
			t.Errorf("case %d: compareJWTs(tt.wantInitialIDToken, oidcAP.initialIDToken)=%v", i, diff)
		}
	}
}
Example #5
0
func TestTLSConfig(t *testing.T) {
	// Verify the cert/key pair works.
	cert1 := path.Join(os.TempDir(), "oidc-cert-1")
	key1 := path.Join(os.TempDir(), "oidc-key-1")
	cert2 := path.Join(os.TempDir(), "oidc-cert-2")
	key2 := path.Join(os.TempDir(), "oidc-key-2")

	defer os.Remove(cert1)
	defer os.Remove(key1)
	defer os.Remove(cert2)
	defer os.Remove(key2)

	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert1, key1)
	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert2, key2)

	tests := []struct {
		testCase string

		serverCertFile string
		serverKeyFile  string

		trustedCertFile string

		wantErr bool
	}{
		{
			testCase:       "provider using untrusted custom cert",
			serverCertFile: cert1,
			serverKeyFile:  key1,
			wantErr:        true,
		},
		{
			testCase:        "provider using untrusted cert",
			serverCertFile:  cert1,
			serverKeyFile:   key1,
			trustedCertFile: cert2,
			wantErr:         true,
		},
		{
			testCase:        "provider using trusted cert",
			serverCertFile:  cert1,
			serverKeyFile:   key1,
			trustedCertFile: cert1,
			wantErr:         false,
		},
	}

	for _, tc := range tests {
		func() {
			op := oidctesting.NewOIDCProvider(t, "")
			srv, err := op.ServeTLSWithKeyPair(tc.serverCertFile, tc.serverKeyFile)
			if err != nil {
				t.Errorf("%s: %v", tc.testCase, err)
				return
			}
			defer srv.Close()

			issuer := srv.URL
			clientID := "client-foo"

			options := OIDCOptions{
				IssuerURL:     srv.URL,
				ClientID:      clientID,
				CAFile:        tc.trustedCertFile,
				UsernameClaim: "email",
				GroupsClaim:   "groups",
			}

			authenticator, err := New(options)
			if err != nil {
				t.Errorf("%s: failed to initialize authenticator: %v", tc.testCase, err)
				return
			}
			defer authenticator.Close()

			email := "*****@*****.**"
			groups := []string{"group1", "group2"}
			sort.Strings(groups)

			token := generateGoodToken(t, op, issuer, "user-1", clientID, "email", email, "groups", groups)

			// Because this authenticator behaves differently for subsequent requests, run these
			// tests multiple times (but expect the same result).
			for i := 1; i < 4; i++ {

				user, ok, err := authenticator.AuthenticateToken(token)
				if err != nil {
					if !tc.wantErr {
						t.Errorf("%s (req #%d): failed to authenticate token: %v", tc.testCase, i, err)
					}
					continue
				}

				if tc.wantErr {
					t.Errorf("%s (req #%d): expected error authenticating", tc.testCase, i)
					continue
				}
				if !ok {
					t.Errorf("%s (req #%d): did not get user or error", tc.testCase, i)
					continue
				}

				if gotUsername := user.GetName(); email != gotUsername {
					t.Errorf("%s (req #%d): GetName() expected=%q got %q", tc.testCase, i, email, gotUsername)
				}
				gotGroups := user.GetGroups()
				sort.Strings(gotGroups)
				if !reflect.DeepEqual(gotGroups, groups) {
					t.Errorf("%s (req #%d): GetGroups() expected=%q got %q", tc.testCase, i, groups, gotGroups)
				}
			}
		}()
	}
}
Example #6
0
func TestOIDCAuthentication(t *testing.T) {
	cert := path.Join(os.TempDir(), "oidc-cert")
	key := path.Join(os.TempDir(), "oidc-key")

	defer os.Remove(cert)
	defer os.Remove(key)

	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)

	// Ensure all tests pass when the issuer is not at a base URL.
	for _, path := range []string{"", "/path/with/trailing/slash/"} {

		// Create a TLS server and a client.
		op := oidctesting.NewOIDCProvider(t, path)
		srv, err := op.ServeTLSWithKeyPair(cert, key)
		if err != nil {
			t.Fatalf("Cannot start server: %v", err)
		}
		defer srv.Close()

		tests := []struct {
			userClaim   string
			groupsClaim string
			token       string
			userInfo    user.Info
			verified    bool
			err         string
		}{
			{
				"sub",
				"",
				generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
				&user.DefaultInfo{Name: fmt.Sprintf("%s#%s", srv.URL, "user-foo")},
				true,
				"",
			},
			{
				// Use user defined claim (email here).
				"email",
				"",
				generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "*****@*****.**", "", nil),
				&user.DefaultInfo{Name: "*****@*****.**"},
				true,
				"",
			},
			{
				// Use user defined claim (email here).
				"email",
				"",
				generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "*****@*****.**", "groups", []string{"group1", "group2"}),
				&user.DefaultInfo{Name: "*****@*****.**"},
				true,
				"",
			},
			{
				// Use user defined claim (email here).
				"email",
				"groups",
				generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "*****@*****.**", "groups", []string{"group1", "group2"}),
				&user.DefaultInfo{Name: "*****@*****.**", Groups: []string{"group1", "group2"}},
				true,
				"",
			},
			{
				// Group claim is a string rather than an array. Map that string to a single group.
				"email",
				"groups",
				generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "*****@*****.**", "groups", "group1"),
				&user.DefaultInfo{Name: "*****@*****.**", Groups: []string{"group1"}},
				true,
				"",
			},
			{
				// Group claim is not a string or array of strings. Throw out this as invalid.
				"email",
				"groups",
				generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "*****@*****.**", "groups", 1),
				nil,
				false,
				"custom group claim contains invalid type: float64",
			},
			{
				"sub",
				"",
				generateMalformedToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
				nil,
				false,
				"oidc: unable to verify JWT signature: no matching keys",
			},
			{
				// Invalid 'aud'.
				"sub",
				"",
				generateGoodToken(t, op, srv.URL, "client-foo", "client-bar", "sub", "user-foo", "", nil),
				nil,
				false,
				"oidc: JWT claims invalid: invalid claims, 'aud' claim and 'client_id' do not match",
			},
			{
				// Invalid issuer.
				"sub",
				"",
				generateGoodToken(t, op, "http://foo-bar.com", "client-foo", "client-foo", "sub", "user-foo", "", nil),
				nil,
				false,
				"oidc: JWT claims invalid: invalid claim value: 'iss'.",
			},
			{
				"sub",
				"",
				generateExpiredToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
				nil,
				false,
				"oidc: JWT claims invalid: token is expired",
			},
		}

		for i, tt := range tests {
			client, err := New(OIDCOptions{srv.URL, "client-foo", cert, tt.userClaim, tt.groupsClaim})
			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				continue
			}

			user, result, err := client.AuthenticateToken(tt.token)
			if tt.err != "" {
				if !strings.HasPrefix(err.Error(), tt.err) {
					t.Errorf("#%d: Expecting: %v..., but got: %v", i, tt.err, err)
				}
			} else {
				if err != nil {
					t.Errorf("#%d: Unexpected error: %v", i, err)
				}
			}
			if !reflect.DeepEqual(tt.verified, result) {
				t.Errorf("#%d: Expecting: %v, but got: %v", i, tt.verified, result)
			}
			if !reflect.DeepEqual(tt.userInfo, user) {
				t.Errorf("#%d: Expecting: %v, but got: %v", i, tt.userInfo, user)
			}
			client.Close()
		}
	}
}
Example #7
0
func TestOIDCAuthentication(t *testing.T) {
	var err error

	cert := path.Join(os.TempDir(), "oidc-cert")
	key := path.Join(os.TempDir(), "oidc-key")

	defer os.Remove(cert)
	defer os.Remove(key)

	oidctesting.GenerateSelfSignedCert(t, "127.0.0.1", cert, key)

	// Create a TLS server and a client.
	op := oidctesting.NewOIDCProvider(t)
	srv, err := op.ServeTLSWithKeyPair(cert, key)
	if err != nil {
		t.Fatalf("Cannot start server: %v", err)
	}
	defer srv.Close()

	// A provider config with all required fields.
	op.AddMinimalProviderConfig(srv)

	tests := []struct {
		userClaim   string
		groupsClaim string
		token       string
		userInfo    user.Info
		verified    bool
		err         string
	}{
		{
			"sub",
			"",
			generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
			&user.DefaultInfo{Name: fmt.Sprintf("%s#%s", srv.URL, "user-foo")},
			true,
			"",
		},
		{
			// Use user defined claim (email here).
			"email",
			"",
			generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "*****@*****.**", "", nil),
			&user.DefaultInfo{Name: "*****@*****.**"},
			true,
			"",
		},
		{
			// Use user defined claim (email here).
			"email",
			"",
			generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "*****@*****.**", "groups", []string{"group1", "group2"}),
			&user.DefaultInfo{Name: "*****@*****.**"},
			true,
			"",
		},
		{
			// Use user defined claim (email here).
			"email",
			"groups",
			generateGoodToken(t, op, srv.URL, "client-foo", "client-foo", "email", "*****@*****.**", "groups", []string{"group1", "group2"}),
			&user.DefaultInfo{Name: "*****@*****.**", Groups: []string{"group1", "group2"}},
			true,
			"",
		},
		{
			"sub",
			"",
			generateMalformedToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
			nil,
			false,
			"oidc: unable to verify JWT signature: no matching keys",
		},
		{
			// Invalid 'aud'.
			"sub",
			"",
			generateGoodToken(t, op, srv.URL, "client-foo", "client-bar", "sub", "user-foo", "", nil),
			nil,
			false,
			"oidc: JWT claims invalid: invalid claims, 'aud' claim and 'client_id' do not match",
		},
		{
			// Invalid issuer.
			"sub",
			"",
			generateGoodToken(t, op, "http://foo-bar.com", "client-foo", "client-foo", "sub", "user-foo", "", nil),
			nil,
			false,
			"oidc: JWT claims invalid: invalid claim value: 'iss'.",
		},
		{
			"sub",
			"",
			generateExpiredToken(t, op, srv.URL, "client-foo", "client-foo", "sub", "user-foo", "", nil),
			nil,
			false,
			"oidc: JWT claims invalid: token is expired",
		},
	}

	for i, tt := range tests {
		client, err := New(OIDCOptions{srv.URL, "client-foo", cert, tt.userClaim, tt.groupsClaim, 1, 100 * time.Millisecond})
		if err != nil {
			t.Errorf("Unexpected error: %v", err)
			continue
		}

		user, result, err := client.AuthenticateToken(tt.token)
		if tt.err != "" {
			if !strings.HasPrefix(err.Error(), tt.err) {
				t.Errorf("#%d: Expecting: %v..., but got: %v", i, tt.err, err)
			}
		} else {
			if err != nil {
				t.Errorf("#%d: Unexpected error: %v", i, err)
			}
		}
		if !reflect.DeepEqual(tt.verified, result) {
			t.Errorf("#%d: Expecting: %v, but got: %v", i, tt.verified, result)
		}
		if !reflect.DeepEqual(tt.userInfo, user) {
			t.Errorf("#%d: Expecting: %v, but got: %v", i, tt.userInfo, user)
		}
		client.Close()
	}
}