diff --git a/api/api.pb.go b/api/api.pb.go
index 9395f484805f2b32860d69e31173a5916178b422..56574c092454a89436c1b040ba12b45eab2cafe4 100644
--- a/api/api.pb.go
+++ b/api/api.pb.go
@@ -31,6 +31,8 @@ It has these top-level messages:
 	ListRefreshResp
 	RevokeRefreshReq
 	RevokeRefreshResp
+	VerifyPasswordReq
+	VerifyPasswordResp
 */
 package api
 
@@ -607,6 +609,54 @@ func (m *RevokeRefreshResp) GetNotFound() bool {
 	return false
 }
 
+type VerifyPasswordReq struct {
+	Email    string `protobuf:"bytes,1,opt,name=email" json:"email,omitempty"`
+	Password string `protobuf:"bytes,2,opt,name=password" json:"password,omitempty"`
+}
+
+func (m *VerifyPasswordReq) Reset()                    { *m = VerifyPasswordReq{} }
+func (m *VerifyPasswordReq) String() string            { return proto.CompactTextString(m) }
+func (*VerifyPasswordReq) ProtoMessage()               {}
+func (*VerifyPasswordReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{23} }
+
+func (m *VerifyPasswordReq) GetEmail() string {
+	if m != nil {
+		return m.Email
+	}
+	return ""
+}
+
+func (m *VerifyPasswordReq) GetPassword() string {
+	if m != nil {
+		return m.Password
+	}
+	return ""
+}
+
+type VerifyPasswordResp struct {
+	Verified bool `protobuf:"varint,1,opt,name=verified" json:"verified,omitempty"`
+	NotFound bool `protobuf:"varint,2,opt,name=not_found,json=notFound" json:"not_found,omitempty"`
+}
+
+func (m *VerifyPasswordResp) Reset()                    { *m = VerifyPasswordResp{} }
+func (m *VerifyPasswordResp) String() string            { return proto.CompactTextString(m) }
+func (*VerifyPasswordResp) ProtoMessage()               {}
+func (*VerifyPasswordResp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{24} }
+
+func (m *VerifyPasswordResp) GetVerified() bool {
+	if m != nil {
+		return m.Verified
+	}
+	return false
+}
+
+func (m *VerifyPasswordResp) GetNotFound() bool {
+	if m != nil {
+		return m.NotFound
+	}
+	return false
+}
+
 func init() {
 	proto.RegisterType((*Client)(nil), "api.Client")
 	proto.RegisterType((*CreateClientReq)(nil), "api.CreateClientReq")
@@ -631,6 +681,8 @@ func init() {
 	proto.RegisterType((*ListRefreshResp)(nil), "api.ListRefreshResp")
 	proto.RegisterType((*RevokeRefreshReq)(nil), "api.RevokeRefreshReq")
 	proto.RegisterType((*RevokeRefreshResp)(nil), "api.RevokeRefreshResp")
+	proto.RegisterType((*VerifyPasswordReq)(nil), "api.VerifyPasswordReq")
+	proto.RegisterType((*VerifyPasswordResp)(nil), "api.VerifyPasswordResp")
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -666,6 +718,8 @@ type DexClient interface {
 	//
 	// Note that each user-client pair can have only one refresh token at a time.
 	RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error)
+	// VerifyPassword returns whether a password matches a hash for a specific email or not.
+	VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error)
 }
 
 type dexClient struct {
@@ -766,6 +820,15 @@ func (c *dexClient) RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opt
 	return out, nil
 }
 
+func (c *dexClient) VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) {
+	out := new(VerifyPasswordResp)
+	err := grpc.Invoke(ctx, "/api.Dex/VerifyPassword", in, out, c.cc, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // Server API for Dex service
 
 type DexServer interface {
@@ -791,6 +854,8 @@ type DexServer interface {
 	//
 	// Note that each user-client pair can have only one refresh token at a time.
 	RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error)
+	// VerifyPassword returns whether a password matches a hash for a specific email or not.
+	VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error)
 }
 
 func RegisterDexServer(s *grpc.Server, srv DexServer) {
@@ -977,6 +1042,24 @@ func _Dex_RevokeRefresh_Handler(srv interface{}, ctx context.Context, dec func(i
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Dex_VerifyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(VerifyPasswordReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).VerifyPassword(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/VerifyPassword",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).VerifyPassword(ctx, req.(*VerifyPasswordReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _Dex_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "api.Dex",
 	HandlerType: (*DexServer)(nil),
@@ -1021,6 +1104,10 @@ var _Dex_serviceDesc = grpc.ServiceDesc{
 			MethodName: "RevokeRefresh",
 			Handler:    _Dex_RevokeRefresh_Handler,
 		},
+		{
+			MethodName: "VerifyPassword",
+			Handler:    _Dex_VerifyPassword_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "api/api.proto",
@@ -1029,58 +1116,62 @@ var _Dex_serviceDesc = grpc.ServiceDesc{
 func init() { proto.RegisterFile("api/api.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-	// 848 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xdb, 0x4e, 0xf3, 0x46,
-	0x10, 0x4e, 0x62, 0x92, 0x38, 0x93, 0xf3, 0x36, 0x3f, 0x84, 0xa0, 0x4a, 0xb0, 0xa8, 0x12, 0xa8,
-	0x52, 0x28, 0x54, 0x6a, 0xa5, 0xa2, 0xd2, 0x03, 0xb4, 0x05, 0xa9, 0xaa, 0x90, 0xd5, 0xf4, 0xb2,
-	0x96, 0x89, 0x07, 0x58, 0x61, 0x6c, 0x77, 0x77, 0x43, 0x68, 0x2f, 0xfb, 0x18, 0x7d, 0x9b, 0xbe,
-	0xd9, 0xaf, 0x5d, 0x6f, 0x82, 0x0f, 0x81, 0x70, 0xe7, 0xf9, 0x76, 0xe6, 0x9b, 0xf3, 0xc8, 0xd0,
-	0xf6, 0x62, 0x76, 0xe4, 0xc5, 0x6c, 0x1c, 0xf3, 0x48, 0x46, 0xc4, 0xf2, 0x62, 0x46, 0xff, 0x2f,
-	0x43, 0xed, 0x3c, 0x60, 0x18, 0x4a, 0xd2, 0x81, 0x0a, 0xf3, 0x87, 0xe5, 0xdd, 0xf2, 0x41, 0xc3,
-	0xa9, 0x30, 0x9f, 0x6c, 0x42, 0x4d, 0xe0, 0x94, 0xa3, 0x1c, 0x56, 0x34, 0x66, 0x24, 0xb2, 0x0f,
-	0x6d, 0x8e, 0x3e, 0xe3, 0x38, 0x95, 0xee, 0x8c, 0x33, 0x31, 0xb4, 0x76, 0xad, 0x83, 0x86, 0xd3,
-	0x5a, 0x80, 0x13, 0xce, 0x84, 0x52, 0x92, 0x7c, 0x26, 0x24, 0xfa, 0x6e, 0x8c, 0xc8, 0xc5, 0x70,
-	0x23, 0x51, 0x32, 0xe0, 0xb5, 0xc2, 0x94, 0x87, 0x78, 0x76, 0x13, 0xb0, 0xe9, 0xb0, 0xba, 0x5b,
-	0x3e, 0xb0, 0x1d, 0x23, 0x11, 0x02, 0x1b, 0xa1, 0xf7, 0x88, 0xc3, 0x9a, 0xf6, 0xab, 0xbf, 0xc9,
-	0x36, 0xd8, 0x41, 0x74, 0x17, 0xb9, 0x33, 0x1e, 0x0c, 0xeb, 0x1a, 0xaf, 0x2b, 0x79, 0xc2, 0x03,
-	0xfa, 0x15, 0x74, 0xcf, 0x39, 0x7a, 0x12, 0x93, 0x44, 0x1c, 0xfc, 0x8b, 0xec, 0x43, 0x6d, 0xaa,
-	0x05, 0x9d, 0x4f, 0xf3, 0xa4, 0x39, 0x56, 0x79, 0x9b, 0x77, 0xf3, 0x44, 0xff, 0x84, 0x5e, 0xd6,
-	0x4e, 0xc4, 0xe4, 0x33, 0xe8, 0x78, 0x01, 0x47, 0xcf, 0xff, 0xdb, 0xc5, 0x67, 0x26, 0xa4, 0xd0,
-	0x04, 0xb6, 0xd3, 0x36, 0xe8, 0x4f, 0x1a, 0x4c, 0xf1, 0x57, 0x5e, 0xe7, 0xdf, 0x83, 0xee, 0x05,
-	0x06, 0x98, 0x8e, 0x2b, 0x57, 0x63, 0x7a, 0x04, 0xbd, 0xac, 0x8a, 0x88, 0xc9, 0x0e, 0x34, 0xc2,
-	0x48, 0xba, 0xb7, 0xd1, 0x2c, 0xf4, 0x8d, 0x77, 0x3b, 0x8c, 0xe4, 0xcf, 0x4a, 0xa6, 0xff, 0x95,
-	0xa1, 0x3b, 0x89, 0x7d, 0xef, 0x0d, 0xd2, 0x62, 0x83, 0x2a, 0xef, 0x69, 0x90, 0xb5, 0xa2, 0x41,
-	0x8b, 0x46, 0x6c, 0xbc, 0xd2, 0x88, 0x6a, 0xb6, 0x11, 0x47, 0xd0, 0xcb, 0xc6, 0xb6, 0x2e, 0x1b,
-	0x06, 0xf6, 0xb5, 0x27, 0xc4, 0x3c, 0xe2, 0x3e, 0x19, 0x40, 0x15, 0x1f, 0x3d, 0x16, 0x98, 0x44,
-	0x12, 0x41, 0x45, 0x70, 0xef, 0x89, 0x7b, 0x5d, 0xe6, 0x96, 0xa3, 0xbf, 0xc9, 0x08, 0xec, 0x99,
-	0x40, 0xae, 0x23, 0xb3, 0xb4, 0xf2, 0x52, 0x26, 0x5b, 0x50, 0x57, 0xdf, 0x2e, 0xf3, 0x4d, 0xd0,
-	0x35, 0x25, 0x5e, 0xf9, 0xf4, 0x0c, 0xfa, 0x49, 0xb3, 0x17, 0x0e, 0x55, 0xe5, 0x0e, 0xc1, 0x8e,
-	0x8d, 0x68, 0x06, 0xa5, 0xad, 0x1b, 0xb9, 0xd4, 0x59, 0x3e, 0xd3, 0x53, 0x20, 0x79, 0xfb, 0x77,
-	0x8f, 0x0b, 0xbd, 0x83, 0x7e, 0x52, 0x98, 0xb4, 0xf3, 0xd5, 0x09, 0x6f, 0x83, 0x1d, 0xe2, 0xdc,
-	0x4d, 0x25, 0x5d, 0x0f, 0x71, 0x7e, 0xa9, 0xf2, 0xde, 0x83, 0x96, 0x7a, 0xca, 0xe5, 0xde, 0x0c,
-	0x71, 0x3e, 0x31, 0x10, 0x3d, 0x06, 0x92, 0x77, 0xb4, 0xae, 0x07, 0x87, 0xd0, 0x4f, 0x46, 0x70,
-	0x6d, 0x6c, 0x8a, 0x3d, 0xaf, 0xba, 0x8e, 0xbd, 0x0f, 0xdd, 0x5f, 0x99, 0x90, 0x29, 0x6e, 0xfa,
-	0x1d, 0xf4, 0xb2, 0x90, 0x88, 0xc9, 0xe7, 0xd0, 0x58, 0x54, 0x5a, 0x95, 0xd0, 0x2a, 0x76, 0xe2,
-	0xe5, 0x9d, 0xb6, 0x00, 0xfe, 0x40, 0x2e, 0x58, 0x14, 0x2a, 0xba, 0xaf, 0xa1, 0xb9, 0x94, 0x44,
-	0x9c, 0x5c, 0x2d, 0xfe, 0x84, 0xdc, 0x84, 0x6e, 0x24, 0xd2, 0x03, 0x75, 0xef, 0x74, 0x49, 0xab,
-	0x8e, 0x3e, 0x7d, 0xff, 0x40, 0xd7, 0xc1, 0x5b, 0x8e, 0xe2, 0xfe, 0xf7, 0xe8, 0x01, 0x43, 0x07,
-	0x6f, 0x0b, 0x9b, 0xb4, 0x03, 0x8d, 0x64, 0x97, 0xd5, 0x3c, 0x25, 0x57, 0xd0, 0x4e, 0x80, 0x2b,
-	0x9f, 0x7c, 0x0a, 0x30, 0xd5, 0x13, 0xe1, 0xbb, 0x9e, 0xd4, 0xab, 0x60, 0x39, 0x0d, 0x83, 0xfc,
-	0x20, 0x95, 0x6d, 0xe0, 0x09, 0xa9, 0xda, 0xe5, 0xeb, 0x4b, 0x66, 0x39, 0xb6, 0x02, 0x26, 0x02,
-	0x55, 0xd1, 0x3b, 0xaa, 0x06, 0xc6, 0xbf, 0xaa, 0x78, 0x6a, 0x70, 0xcb, 0x99, 0xc1, 0xfd, 0x2d,
-	0xa9, 0xe0, 0x52, 0x55, 0xc4, 0xe4, 0x14, 0x3a, 0x3c, 0x11, 0x5d, 0xa9, 0x42, 0x5f, 0x94, 0x6c,
-	0xa0, 0x4b, 0x96, 0x4b, 0xca, 0x69, 0xf3, 0x14, 0x20, 0xe8, 0x25, 0xf4, 0x1c, 0x7c, 0x8a, 0x1e,
-	0xf0, 0x1d, 0xce, 0xdf, 0x2c, 0x00, 0xfd, 0x02, 0xfa, 0x39, 0xa6, 0x35, 0xd3, 0x70, 0xf2, 0x6f,
-	0x15, 0xac, 0x0b, 0x7c, 0x26, 0xdf, 0x42, 0x2b, 0x7d, 0x79, 0x49, 0x12, 0x78, 0xee, 0x88, 0x8f,
-	0x3e, 0xac, 0x40, 0x45, 0x4c, 0x4b, 0xca, 0x3c, 0x7d, 0x67, 0x8c, 0x79, 0xee, 0x2c, 0x1a, 0xf3,
-	0xfc, 0x41, 0x4a, 0xcc, 0xd3, 0x47, 0xd7, 0x98, 0xe7, 0x4e, 0xb5, 0x31, 0xcf, 0x5f, 0x67, 0x5a,
-	0x22, 0xe7, 0xd0, 0xc9, 0x5e, 0x02, 0xb2, 0x99, 0x0a, 0x34, 0x35, 0xe9, 0xa3, 0xad, 0x95, 0xf8,
-	0x82, 0x24, 0xbb, 0xa8, 0x86, 0xa4, 0x70, 0x26, 0x0c, 0x49, 0x71, 0xab, 0x13, 0x92, 0xec, 0x3e,
-	0x1a, 0x92, 0xc2, 0x3e, 0x1b, 0x92, 0xe2, 0xf2, 0xd2, 0x12, 0x39, 0x83, 0x76, 0x7a, 0x1d, 0x85,
-	0x29, 0x47, 0x6e, 0x6b, 0x4d, 0x39, 0xf2, 0x8b, 0x4b, 0x4b, 0xe4, 0x18, 0xe0, 0x17, 0x94, 0x66,
-	0x05, 0x49, 0x57, 0xab, 0xbd, 0xac, 0xe7, 0xa8, 0x97, 0x05, 0xb4, 0xc9, 0x37, 0xd0, 0x4c, 0x8d,
-	0x34, 0xf9, 0x64, 0x49, 0xfd, 0x32, 0x92, 0xa3, 0x41, 0x11, 0xd4, 0xb6, 0xdf, 0x43, 0x3b, 0x33,
-	0x74, 0xe4, 0x83, 0x19, 0xfa, 0xec, 0x48, 0x8f, 0x36, 0x57, 0xc1, 0x8a, 0xe1, 0xc7, 0x01, 0x90,
-	0x69, 0xf4, 0x38, 0x9e, 0x46, 0x1c, 0x23, 0x31, 0xf6, 0xf1, 0x59, 0x69, 0xde, 0xd4, 0xf4, 0x4f,
-	0xd1, 0x97, 0x1f, 0x03, 0x00, 0x00, 0xff, 0xff, 0xd1, 0x00, 0x08, 0xae, 0x25, 0x09, 0x00, 0x00,
+	// 905 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x56, 0xeb, 0x6e, 0xdb, 0x36,
+	0x14, 0xb6, 0xad, 0xd8, 0x96, 0x8f, 0xef, 0x9c, 0x9b, 0xba, 0x2e, 0x06, 0xa4, 0x2c, 0x06, 0xa4,
+	0x18, 0xe0, 0xac, 0x1d, 0xb0, 0x01, 0x2b, 0xd6, 0x5d, 0xd2, 0x6e, 0x2d, 0xb0, 0x0d, 0x85, 0x30,
+	0xe7, 0xe7, 0x04, 0xc5, 0x3a, 0x4e, 0x88, 0x28, 0x92, 0x46, 0xd2, 0x71, 0xb2, 0x47, 0xd9, 0xdb,
+	0xec, 0xd7, 0x5e, 0xab, 0x20, 0x45, 0x29, 0xba, 0x38, 0x71, 0xfe, 0xf9, 0x7c, 0xe2, 0xb9, 0x7d,
+	0x87, 0xe7, 0xa3, 0xa1, 0xef, 0xc5, 0xec, 0xc8, 0x8b, 0xd9, 0x3c, 0xe6, 0x91, 0x8c, 0x88, 0xe5,
+	0xc5, 0x8c, 0xfe, 0x57, 0x87, 0xd6, 0x71, 0xc0, 0x30, 0x94, 0x64, 0x00, 0x0d, 0xe6, 0x4f, 0xeb,
+	0x07, 0xf5, 0xc3, 0x8e, 0xd3, 0x60, 0x3e, 0xd9, 0x87, 0x96, 0xc0, 0x25, 0x47, 0x39, 0x6d, 0x68,
+	0xcc, 0x58, 0xe4, 0x39, 0xf4, 0x39, 0xfa, 0x8c, 0xe3, 0x52, 0xba, 0x6b, 0xce, 0xc4, 0xd4, 0x3a,
+	0xb0, 0x0e, 0x3b, 0x4e, 0x2f, 0x05, 0x17, 0x9c, 0x09, 0x75, 0x48, 0xf2, 0xb5, 0x90, 0xe8, 0xbb,
+	0x31, 0x22, 0x17, 0xd3, 0xbd, 0xe4, 0x90, 0x01, 0x3f, 0x2a, 0x4c, 0x65, 0x88, 0xd7, 0xa7, 0x01,
+	0x5b, 0x4e, 0x9b, 0x07, 0xf5, 0x43, 0xdb, 0x31, 0x16, 0x21, 0xb0, 0x17, 0x7a, 0x97, 0x38, 0x6d,
+	0xe9, 0xbc, 0xfa, 0x37, 0x79, 0x02, 0x76, 0x10, 0x9d, 0x45, 0xee, 0x9a, 0x07, 0xd3, 0xb6, 0xc6,
+	0xdb, 0xca, 0x5e, 0xf0, 0x80, 0x7e, 0x03, 0xc3, 0x63, 0x8e, 0x9e, 0xc4, 0xa4, 0x11, 0x07, 0xff,
+	0x26, 0xcf, 0xa1, 0xb5, 0xd4, 0x86, 0xee, 0xa7, 0xfb, 0xaa, 0x3b, 0x57, 0x7d, 0x9b, 0xef, 0xe6,
+	0x13, 0xfd, 0x0b, 0x46, 0x45, 0x3f, 0x11, 0x93, 0x2f, 0x60, 0xe0, 0x05, 0x1c, 0x3d, 0xff, 0xc6,
+	0xc5, 0x6b, 0x26, 0xa4, 0xd0, 0x01, 0x6c, 0xa7, 0x6f, 0xd0, 0x77, 0x1a, 0xcc, 0xc5, 0x6f, 0xdc,
+	0x1d, 0xff, 0x19, 0x0c, 0xdf, 0x62, 0x80, 0xf9, 0xba, 0x4a, 0x1c, 0xd3, 0x23, 0x18, 0x15, 0x8f,
+	0x88, 0x98, 0x3c, 0x85, 0x4e, 0x18, 0x49, 0x77, 0x15, 0xad, 0x43, 0xdf, 0x64, 0xb7, 0xc3, 0x48,
+	0xfe, 0xa2, 0x6c, 0xfa, 0x6f, 0x1d, 0x86, 0x8b, 0xd8, 0xf7, 0xee, 0x09, 0x5a, 0x1d, 0x50, 0xe3,
+	0x21, 0x03, 0xb2, 0xb6, 0x0c, 0x28, 0x1d, 0xc4, 0xde, 0x1d, 0x83, 0x68, 0x16, 0x07, 0x71, 0x04,
+	0xa3, 0x62, 0x6d, 0xbb, 0xba, 0x61, 0x60, 0x7f, 0xf4, 0x84, 0xd8, 0x44, 0xdc, 0x27, 0x13, 0x68,
+	0xe2, 0xa5, 0xc7, 0x02, 0xd3, 0x48, 0x62, 0xa8, 0x0a, 0xce, 0x3d, 0x71, 0xae, 0x69, 0xee, 0x39,
+	0xfa, 0x37, 0x99, 0x81, 0xbd, 0x16, 0xc8, 0x75, 0x65, 0x96, 0x3e, 0x9c, 0xd9, 0xe4, 0x31, 0xb4,
+	0xd5, 0x6f, 0x97, 0xf9, 0xa6, 0xe8, 0x96, 0x32, 0x3f, 0xf8, 0xf4, 0x0d, 0x8c, 0x93, 0x61, 0xa7,
+	0x09, 0x15, 0x73, 0x2f, 0xc0, 0x8e, 0x8d, 0x69, 0x2e, 0x4a, 0x5f, 0x0f, 0x32, 0x3b, 0x93, 0x7d,
+	0xa6, 0xaf, 0x81, 0x94, 0xfd, 0x1f, 0x7c, 0x5d, 0xe8, 0x19, 0x8c, 0x13, 0x62, 0xf2, 0xc9, 0xb7,
+	0x37, 0xfc, 0x04, 0xec, 0x10, 0x37, 0x6e, 0xae, 0xe9, 0x76, 0x88, 0x9b, 0xf7, 0xaa, 0xef, 0x67,
+	0xd0, 0x53, 0x9f, 0x4a, 0xbd, 0x77, 0x43, 0xdc, 0x2c, 0x0c, 0x44, 0x5f, 0x02, 0x29, 0x27, 0xda,
+	0x35, 0x83, 0x17, 0x30, 0x4e, 0xae, 0xe0, 0xce, 0xda, 0x54, 0xf4, 0xf2, 0xd1, 0x5d, 0xd1, 0xc7,
+	0x30, 0xfc, 0x8d, 0x09, 0x99, 0x8b, 0x4d, 0x7f, 0x80, 0x51, 0x11, 0x12, 0x31, 0xf9, 0x12, 0x3a,
+	0x29, 0xd3, 0x8a, 0x42, 0xab, 0x3a, 0x89, 0xdb, 0xef, 0xb4, 0x07, 0x70, 0x82, 0x5c, 0xb0, 0x28,
+	0x54, 0xe1, 0xbe, 0x85, 0x6e, 0x66, 0x89, 0x38, 0x51, 0x2d, 0x7e, 0x85, 0xdc, 0x94, 0x6e, 0x2c,
+	0x32, 0x02, 0xa5, 0x77, 0x9a, 0xd2, 0xa6, 0xa3, 0xa5, 0xef, 0x1f, 0x18, 0x3a, 0xb8, 0xe2, 0x28,
+	0xce, 0xff, 0x8c, 0x2e, 0x30, 0x74, 0x70, 0x55, 0xd9, 0xa4, 0xa7, 0xd0, 0x49, 0x76, 0x59, 0xdd,
+	0xa7, 0x44, 0x05, 0xed, 0x04, 0xf8, 0xe0, 0x93, 0xcf, 0x01, 0x96, 0xfa, 0x46, 0xf8, 0xae, 0x27,
+	0xf5, 0x2a, 0x58, 0x4e, 0xc7, 0x20, 0x3f, 0x49, 0xe5, 0x1b, 0x78, 0x42, 0xaa, 0x71, 0xf9, 0x5a,
+	0xc9, 0x2c, 0xc7, 0x56, 0xc0, 0x42, 0xa0, 0x22, 0x7d, 0xa0, 0x38, 0x30, 0xf9, 0x15, 0xe3, 0xb9,
+	0x8b, 0x5b, 0x2f, 0x5c, 0xdc, 0x3f, 0x12, 0x06, 0xb3, 0xa3, 0x22, 0x26, 0xaf, 0x61, 0xc0, 0x13,
+	0xd3, 0x95, 0xaa, 0xf4, 0x94, 0xb2, 0x89, 0xa6, 0xac, 0xd4, 0x94, 0xd3, 0xe7, 0x39, 0x40, 0xd0,
+	0xf7, 0x30, 0x72, 0xf0, 0x2a, 0xba, 0xc0, 0x07, 0x24, 0xbf, 0x97, 0x00, 0xfa, 0x15, 0x8c, 0x4b,
+	0x91, 0x76, 0xdd, 0x86, 0x77, 0x30, 0x3e, 0x41, 0xce, 0x56, 0x37, 0xbb, 0xf7, 0x60, 0x96, 0x5b,
+	0x4d, 0x93, 0x38, 0xdb, 0xc5, 0xdf, 0x81, 0x94, 0xc3, 0x88, 0x58, 0x79, 0x5c, 0x29, 0x94, 0x61,
+	0x96, 0x38, 0xb5, 0x8b, 0x55, 0x35, 0x8a, 0x55, 0xbd, 0xfa, 0xbf, 0x09, 0xd6, 0x5b, 0xbc, 0x26,
+	0xdf, 0x43, 0x2f, 0xff, 0x1e, 0x90, 0x84, 0xce, 0xd2, 0xd3, 0x32, 0x7b, 0xb4, 0x05, 0x15, 0x31,
+	0xad, 0x29, 0xf7, 0xbc, 0xfa, 0x19, 0xf7, 0x92, 0x58, 0x1b, 0xf7, 0xb2, 0x4c, 0x26, 0xee, 0xf9,
+	0xa7, 0xc0, 0xb8, 0x97, 0x1e, 0x10, 0xe3, 0x5e, 0x7e, 0x33, 0x68, 0x8d, 0x1c, 0xc3, 0xa0, 0xa8,
+	0x4f, 0x64, 0x3f, 0x57, 0x68, 0x8e, 0xef, 0xd9, 0xe3, 0xad, 0x78, 0x1a, 0xa4, 0x28, 0x1f, 0x26,
+	0x48, 0x45, 0xbc, 0x4c, 0x90, 0xaa, 0xd6, 0x24, 0x41, 0x8a, 0x2a, 0x61, 0x82, 0x54, 0x54, 0xc6,
+	0x04, 0xa9, 0x4a, 0x0a, 0xad, 0x91, 0x37, 0xd0, 0xcf, 0x8b, 0x84, 0x30, 0x74, 0x94, 0xb4, 0xc4,
+	0xd0, 0x51, 0x96, 0x13, 0x5a, 0x23, 0x2f, 0x01, 0x7e, 0x45, 0x69, 0x84, 0x81, 0x0c, 0xf5, 0xb1,
+	0x5b, 0xd1, 0x98, 0x8d, 0x8a, 0x80, 0x76, 0xf9, 0x0e, 0xba, 0xb9, 0x45, 0x23, 0x9f, 0x65, 0xa1,
+	0x6f, 0x17, 0x65, 0x36, 0xa9, 0x82, 0xda, 0xf7, 0x47, 0xe8, 0x17, 0x56, 0x81, 0x3c, 0x32, 0xab,
+	0x58, 0x5c, 0xb4, 0xd9, 0xfe, 0x36, 0x38, 0x65, 0xad, 0x78, 0xa7, 0x0d, 0x6b, 0x95, 0x7d, 0x31,
+	0xac, 0x55, 0x17, 0x80, 0xd6, 0x7e, 0x9e, 0x00, 0x59, 0x46, 0x97, 0xf3, 0x65, 0xc4, 0x31, 0x12,
+	0x73, 0x1f, 0xaf, 0xd5, 0xd1, 0xd3, 0x96, 0xfe, 0xbf, 0xf7, 0xf5, 0xa7, 0x00, 0x00, 0x00, 0xff,
+	0xff, 0x49, 0x46, 0x0e, 0xa3, 0x00, 0x0a, 0x00, 0x00,
 }
diff --git a/api/api.proto b/api/api.proto
index 7b91e822fe3d5d1b11ceb49a017fc95447e72890..5d9ce1a1e35295d2f59819f9c40dcb187314c5db 100644
--- a/api/api.proto
+++ b/api/api.proto
@@ -148,6 +148,16 @@ message RevokeRefreshResp {
   bool not_found = 1;
 }
 
+message VerifyPasswordReq {
+  string email = 1;
+  string password = 2;
+}
+
+message VerifyPasswordResp {
+  bool verified = 1;
+  bool not_found = 2;
+}
+
 // Dex represents the dex gRPC service.
 service Dex {
   // CreateClient creates a client.
@@ -172,4 +182,6 @@ service Dex {
   //
   // Note that each user-client pair can have only one refresh token at a time.
   rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {};
+  // VerifyPassword returns whether a password matches a hash for a specific email or not.
+  rpc VerifyPassword(VerifyPasswordReq) returns (VerifyPasswordResp) {};
 }
diff --git a/examples/grpc-client/README.md b/examples/grpc-client/README.md
index bd39219b0da8ac19e895404510da911ca86a7cdd..460a5d1c6f381d29f6e81c5cc037a05234fbd093 100644
--- a/examples/grpc-client/README.md
+++ b/examples/grpc-client/README.md
@@ -48,7 +48,8 @@ Finally run the Dex client providing the CA certificate, client certificate and
 Running the gRPC client will cause the following API calls to be made to the server
 1. CreatePassword
 2. ListPasswords
-3. DeletePassword
+3. VerifyPassword
+4. DeletePassword
 
 ## Cleaning up
 
diff --git a/examples/grpc-client/client.go b/examples/grpc-client/client.go
index ba976f5eccbb3cbf3990aa245f8a9ab73f018ef2..e4cd526d28ebc425b47c9134b39f8c49099744f9 100644
--- a/examples/grpc-client/client.go
+++ b/examples/grpc-client/client.go
@@ -76,6 +76,39 @@ func createPassword(cli api.DexClient) error {
 		log.Printf("%+v", pass)
 	}
 
+	// Verifying correct and incorrect passwords
+	log.Print("Verifying Password:\n")
+	verifyReq := &api.VerifyPasswordReq{
+		Email:    "test@example.com",
+		Password: "test1",
+	}
+	verifyResp, err := cli.VerifyPassword(context.TODO(), verifyReq)
+	if err != nil {
+		return fmt.Errorf("failed to run VerifyPassword for correct password: %v", err)
+	}
+	if !verifyResp.Verified {
+		return fmt.Errorf("failed to verify correct password: %v", verifyResp)
+	}
+	log.Printf("properly verified correct password: %t\n", verifyResp.Verified)
+
+	badVerifyReq := &api.VerifyPasswordReq{
+		Email:    "test@example.com",
+		Password: "wrong_password",
+	}
+	badVerifyResp, err := cli.VerifyPassword(context.TODO(), badVerifyReq)
+	if err != nil {
+		return fmt.Errorf("failed to run VerifyPassword for incorrect password: %v", err)
+	}
+	if badVerifyResp.Verified {
+		return fmt.Errorf("verify returned true for incorrect password: %v", badVerifyResp)
+	}
+	log.Printf("properly failed to verify incorrect password: %t\n", badVerifyResp.Verified)
+
+	log.Print("Listing Passwords:\n")
+	for _, pass := range resp.Passwords {
+		log.Printf("%+v", pass)
+	}
+
 	deleteReq := &api.DeletePasswordReq{
 		Email: p.Email,
 	}
diff --git a/server/api.go b/server/api.go
index f900cd1c96800a6fcb3b78b2e9f17d1073fd08c4..850ec49313c4b5e659be74c544b83160deea3149 100644
--- a/server/api.go
+++ b/server/api.go
@@ -254,6 +254,37 @@ func (d dexAPI) ListPasswords(ctx context.Context, req *api.ListPasswordReq) (*a
 
 }
 
+func (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) (*api.VerifyPasswordResp, error) {
+	if req.Email == "" {
+		return nil, errors.New("no email supplied")
+	}
+
+	if req.Password == "" {
+		return nil, errors.New("no password to verify supplied")
+	}
+
+	password, err := d.s.GetPassword(req.Email)
+	if err != nil {
+		if err == storage.ErrNotFound {
+			return &api.VerifyPasswordResp{
+				NotFound: true,
+			}, nil
+		}
+		d.logger.Errorf("api: there was an error retrieving the password: %v", err)
+		return nil, fmt.Errorf("verify password: %v", err)
+	}
+
+	if err := bcrypt.CompareHashAndPassword(password.Hash, []byte(req.Password)); err != nil {
+		d.logger.Info("api: password check failed : %v", err)
+		return &api.VerifyPasswordResp{
+			Verified: false,
+		}, nil
+	}
+	return &api.VerifyPasswordResp{
+		Verified: true,
+	}, nil
+}
+
 func (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) {
 	id := new(internal.IDTokenSubject)
 	if err := internal.Unmarshal(req.UserId, id); err != nil {
diff --git a/server/api_test.go b/server/api_test.go
index 568748bf035f37a17e3c68e9d657ed56c34dd0c9..80a224861db631de0db429beb72730c8bd278494 100644
--- a/server/api_test.go
+++ b/server/api_test.go
@@ -69,8 +69,9 @@ func TestPassword(t *testing.T) {
 	defer client.Close()
 
 	ctx := context.Background()
+	email := "test@example.com"
 	p := api.Password{
-		Email: "test@example.com",
+		Email: email,
 		// bcrypt hash of the value "test1" with cost 10
 		Hash:     []byte("$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO"),
 		Username: "test",
@@ -93,8 +94,56 @@ func TestPassword(t *testing.T) {
 		t.Fatalf("Created password %s twice", createReq.Password.Email)
 	}
 
+	// Attempt to verify valid password and email
+	goodVerifyReq := &api.VerifyPasswordReq{
+		Email:    email,
+		Password: "test1",
+	}
+	goodVerifyResp, err := client.VerifyPassword(ctx, goodVerifyReq)
+	if err != nil {
+		t.Fatalf("Unable to run verify password we expected to be valid for correct email: %v", err)
+	}
+	if !goodVerifyResp.Verified {
+		t.Fatalf("verify password failed for password expected to be valid for correct email. expected %t, found %t", true, goodVerifyResp.Verified)
+	}
+	if goodVerifyResp.NotFound {
+		t.Fatalf("verify password failed to return not found response. expected %t, found %t", false, goodVerifyResp.NotFound)
+	}
+
+	// Check not found response for valid password with wrong email
+	badEmailVerifyReq := &api.VerifyPasswordReq{
+		Email:    "somewrongaddress@email.com",
+		Password: "test1",
+	}
+	badEmailVerifyResp, err := client.VerifyPassword(ctx, badEmailVerifyReq)
+	if err != nil {
+		t.Fatalf("Unable to run verify password for incorrect email: %v", err)
+	}
+	if badEmailVerifyResp.Verified {
+		t.Fatalf("verify password passed for password expected to be not found. expected %t, found %t", false, badEmailVerifyResp.Verified)
+	}
+	if !badEmailVerifyResp.NotFound {
+		t.Fatalf("expected not found response for verify password with bad email. expected %t, found %t", true, badEmailVerifyResp.NotFound)
+	}
+
+	// Check that wrong password fails
+	badPassVerifyReq := &api.VerifyPasswordReq{
+		Email:    email,
+		Password: "wrong_password",
+	}
+	badPassVerifyResp, err := client.VerifyPassword(ctx, badPassVerifyReq)
+	if err != nil {
+		t.Fatalf("Unable to run verify password for password we expected to be invalid: %v", err)
+	}
+	if badPassVerifyResp.Verified {
+		t.Fatalf("verify password passed for password we expected to fail. expected %t, found %t", false, badPassVerifyResp.Verified)
+	}
+	if badPassVerifyResp.NotFound {
+		t.Fatalf("did not expect expected not found response for verify password with bad email. expected %t, found %t", false, badPassVerifyResp.NotFound)
+	}
+
 	updateReq := api.UpdatePasswordReq{
-		Email:       "test@example.com",
+		Email:       email,
 		NewUsername: "test1",
 	}