Repository: dexidp/dex
Pull request #1750 (2020-07-01 12:39:57)

API v2 v2
While playing with the v2 API, I decided to try an alternative solution for the separate module. Although it's already on master, we can still play with it until we tag a release.

This approach will actually make Dex fully backwards compatible (the current approach will not work for users importing both the Dex project and the api package very well, see  #1726 for details).

The idea is to move the current API code to a `v2` sub directory under the api package and create a module there. The advantage is backward compatibility and versioning, since this is actually a second API version.

Another advantage is that we can easily introduce a v3 version, if necessary. (Which I think we should do at some point. The package still says coreos, there are a number of requested features on the issue tracker).

On a related note: the new protobuf library for Go now requires `go_package` option to be set, which means rendering proto files of other projects becomes really hard. Providing api packages/modules like this will be the standard way in the future.

The code in the api package is duplicated and copied from api/v2. The api package should be deprecated in the next release.

WDYT?


Patch:
diff --git a/Documentation/api.md b/Documentation/api.md
index 989f5655..ded16ce3 100644
--- a/Documentation/api.md
+++ b/Documentation/api.md
@@ -1,47 +1,50 @@
-# The dex API
+# The Dex API
 
-Dex provides a [gRPC][grpc] service for programmatic modification of dex's state. The API is intended to expose hooks for management applications and is not expected to be used by most installations.
+Dex provides a [gRPC](http://www.grpc.io/) service for programmatic modification of dex's state.
+The API is intended to expose hooks for management applications and is not expected to be used by most installations.
 
 This document is an overview of how to interact with the API.
 
+
 ## Configuration
 
 Admins that wish to expose the gRPC service must add the following entry to the dex config file. This option is off by default.
 
-```
+```yaml
 grpc:
   # Cannot be the same address as an HTTP(S) service.
   addr: 127.0.0.1:5557
+
   # Server certs. If TLS credentials aren't provided dex will run in plaintext (HTTP) mode.
   tlsCert: /etc/dex/grpc.crt
   tlsKey: /etc/dex/grpc.key
+
   # Client auth CA.
   tlsClientCA: /etc/dex/client.crt
+
   # enable reflection
   reflection: true
 ```
 
-## Generating clients
 
-gRPC is a suite of tools for generating client and server bindings from a common declarative language. The canonical schema for dex's API can be found in the source tree at [`api/api.proto`][api-proto]. Go bindings are generated and maintained in the same directory for internal use.
+## Clients
 
-To generate a client for your own project install [`protoc`][protoc], install a protobuf generator for your project's language, and download the `api.proto` file. An example for a Go project:
+gRPC is a suite of tools for generating client and server bindings from a common declarative language.
+The canonical schema for Dex's API can be found in the source tree at [`api/v2/api.proto`](../api/v2/api.proto).
+Go bindings are generated and maintained in the same directory for both public and internal use.
 
-```
-# Install protoc-gen-go.
-$ go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
 
-# Download api.proto for a given version.
-$ DEX_VERSION=v2.0.0-alpha.5
-$ wget https://raw.githubusercontent.com/dexidp/dex/${DEX_VERSION}/api/api.proto
+### Go
+
+A Go project can import the API module directly, without having to import the entire project:
 
-# Generate the Go client bindings.
-$ protoc --go_out=import_path=dexapi:. api.proto
+```bash
+go get github.com/dexidp/dex/api/v2
 ```
 
-Client programs can then be written using the generated code. A Go client which uses dex's internally generated code might look like the following:
+The client then can be used as follows:
 
-```
+```go
 package main
 
 import (
@@ -49,7 +52,7 @@ import (
     "fmt"
     "log"
 
-    "github.com/dexidp/dex/api"
+    "github.com/dexidp/dex/api/v2"
     "google.golang.org/grpc"
     "google.golang.org/grpc/credentials"
 )
@@ -88,30 +91,56 @@ func main() {
 }
 ```
 
-A clear working example of the Dex gRPC client can be found [here](../examples/grpc-client/README.md).
+A clear working example of the Dex gRPC client for Go can be found [here](../examples/grpc-client/README.md).
+
+
+### Other languages
+
+To generate a client for your own project install [`protoc`](https://github.com/google/protobuf/releases),
+install a protobuf generator for your project's language, and download the `api.proto` file.
+
+Here is an example:
+
+```bash
+# Download api.proto for a given version.
+$ DEX_VERSION=v2.24.0
+$ wget https://raw.githubusercontent.com/dexidp/dex/${DEX_VERSION}/api/v2/api.proto
+
+# Generate the client bindings.
+$ protoc [YOUR LANG PARAMS] api.proto
+```
+
+Client programs can then be written using the generated code.
+
 
 ## Authentication and access control
 
-The dex API does not provide any authentication or authorization beyond TLS client auth.
+The Dex API does not provide any authentication or authorization beyond TLS client auth.
+
+Projects that wish to add access controls on top of the existing API should build apps which perform such checks.
+For example to provide a "Change password" screen, a client app could use Dex's OpenID Connect flow to authenticate an end user,
+then call Dex's API to update that user's password.
 
-Projects that wish to add access controls on top of the existing API should build apps which perform such checks. For example to provide a "Change password" screen, a client app could use dex's OpenID Connect flow to authenticate an end user, then call dex's API to update that user's password.
 
 ## dexctl?
 
-Dex does not ship with a command line tool for interacting with the API. Command line tools are useful but hard to version, easy to design poorly, and expose another interface which can never be changed in the name of compatibility.
+Dex does not ship with a command line tool for interacting with the API.
+Command line tools are useful but hard to version, easy to design poorly,
+and expose another interface which can never be changed in the name of compatibility.
 
-While the dex team would be open to re-implementing `dexctl` for v2 a majority of the work is writing a design document, not the actual programming effort.
+While the Dex team would be open to re-implementing `dexctl` for v2 a majority of the work is writing a design document,
+not the actual programming effort.
 
-## Why not REST or gRPC Gateway?
 
-Between v1 and v2, dex switched from REST to gRPC. This largely stemmed from problems generating documentation, client bindings, and server frameworks that adequately expressed REST semantics. While [Google APIs][google-apis], [Open API/Swagger][open-api], and [gRPC Gateway][grpc-gateway] were evaluated, they often became clunky when trying to use specific HTTP error codes or complex request bodies. As a result, v2's API is entirely gRPC.
+## Why not REST or gRPC Gateway?
 
-Many arguments _against_ gRPC cite short term convenience rather than production use cases. Though this is a recognized shortcoming, dex already implements many features for developer convenience. For instance, users who wish to manually edit clients during testing can use the `staticClients` config field instead of the API.
+Between v1 and v2, Dex switched from REST to gRPC. This largely stemmed from problems generating documentation,
+client bindings, and server frameworks that adequately expressed REST semantics.
+While [Google APIs](https://github.com/google/apis-client-generator), [Open API/Swagger](https://openapis.org/),
+and [gRPC Gateway](https://github.com/grpc-ecosystem/grpc-gateway) were evaluated,
+they often became clunky when trying to use specific HTTP error codes or complex request bodies.
+As a result, v2's API is entirely gRPC.
 
-[grpc]: http://www.grpc.io/
-[api-proto]: ../api/api.proto
-[protoc]: https://github.com/google/protobuf/releases
-[protoc-gen-go]: https://github.com/golang/protobuf
-[google-apis]: https://github.com/google/apis-client-generator
-[open-api]: https://openapis.org/
-[grpc-gateway]: https://github.com/grpc-ecosystem/grpc-gateway
+Many arguments _against_ gRPC cite short term convenience rather than production use cases.
+Though this is a recognized shortcoming, Dex already implements many features for developer convenience.
+For instance, users who wish to manually edit clients during testing can use the `staticClients` config field instead of the API.
diff --git a/Makefile b/Makefile
index 7bd0826c..9af8c7f6 100644
--- a/Makefile
+++ b/Makefile
@@ -74,6 +74,8 @@ docker-image:
 
 .PHONY: proto
 proto: bin/protoc bin/protoc-gen-go
+	@./bin/protoc --go_out=plugins=grpc:. --plugin=protoc-gen-go=./bin/protoc-gen-go api/v2/*.proto
+	@cp api/v2/*.proto api/
 	@./bin/protoc --go_out=plugins=grpc:. --plugin=protoc-gen-go=./bin/protoc-gen-go api/*.proto
 	@./bin/protoc --go_out=. --plugin=protoc-gen-go=./bin/protoc-gen-go server/internal/*.proto
 
diff --git a/api/v2/api.pb.go b/api/v2/api.pb.go
new file mode 100644
index 00000000..203407f9
--- /dev/null
+++ b/api/v2/api.pb.go
@@ -0,0 +1,1758 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: api/v2/api.proto
+
+package api
+
+import (
+	context "context"
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// Client represents an OAuth2 client.
+type Client struct {
+	Id                   string   `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	Secret               string   `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"`
+	RedirectUris         []string `protobuf:"bytes,3,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"`
+	TrustedPeers         []string `protobuf:"bytes,4,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"`
+	Public               bool     `protobuf:"varint,5,opt,name=public,proto3" json:"public,omitempty"`
+	Name                 string   `protobuf:"bytes,6,opt,name=name,proto3" json:"name,omitempty"`
+	LogoUrl              string   `protobuf:"bytes,7,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Client) Reset()         { *m = Client{} }
+func (m *Client) String() string { return proto.CompactTextString(m) }
+func (*Client) ProtoMessage()    {}
+func (*Client) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{0}
+}
+
+func (m *Client) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Client.Unmarshal(m, b)
+}
+func (m *Client) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Client.Marshal(b, m, deterministic)
+}
+func (m *Client) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Client.Merge(m, src)
+}
+func (m *Client) XXX_Size() int {
+	return xxx_messageInfo_Client.Size(m)
+}
+func (m *Client) XXX_DiscardUnknown() {
+	xxx_messageInfo_Client.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Client proto.InternalMessageInfo
+
+func (m *Client) GetId() string {
+	if m != nil {
+		return m.Id
+	}
+	return ""
+}
+
+func (m *Client) GetSecret() string {
+	if m != nil {
+		return m.Secret
+	}
+	return ""
+}
+
+func (m *Client) GetRedirectUris() []string {
+	if m != nil {
+		return m.RedirectUris
+	}
+	return nil
+}
+
+func (m *Client) GetTrustedPeers() []string {
+	if m != nil {
+		return m.TrustedPeers
+	}
+	return nil
+}
+
+func (m *Client) GetPublic() bool {
+	if m != nil {
+		return m.Public
+	}
+	return false
+}
+
+func (m *Client) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *Client) GetLogoUrl() string {
+	if m != nil {
+		return m.LogoUrl
+	}
+	return ""
+}
+
+// CreateClientReq is a request to make a client.
+type CreateClientReq struct {
+	Client               *Client  `protobuf:"bytes,1,opt,name=client,proto3" json:"client,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CreateClientReq) Reset()         { *m = CreateClientReq{} }
+func (m *CreateClientReq) String() string { return proto.CompactTextString(m) }
+func (*CreateClientReq) ProtoMessage()    {}
+func (*CreateClientReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{1}
+}
+
+func (m *CreateClientReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CreateClientReq.Unmarshal(m, b)
+}
+func (m *CreateClientReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CreateClientReq.Marshal(b, m, deterministic)
+}
+func (m *CreateClientReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CreateClientReq.Merge(m, src)
+}
+func (m *CreateClientReq) XXX_Size() int {
+	return xxx_messageInfo_CreateClientReq.Size(m)
+}
+func (m *CreateClientReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_CreateClientReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CreateClientReq proto.InternalMessageInfo
+
+func (m *CreateClientReq) GetClient() *Client {
+	if m != nil {
+		return m.Client
+	}
+	return nil
+}
+
+// CreateClientResp returns the response from creating a client.
+type CreateClientResp struct {
+	AlreadyExists        bool     `protobuf:"varint,1,opt,name=already_exists,json=alreadyExists,proto3" json:"already_exists,omitempty"`
+	Client               *Client  `protobuf:"bytes,2,opt,name=client,proto3" json:"client,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CreateClientResp) Reset()         { *m = CreateClientResp{} }
+func (m *CreateClientResp) String() string { return proto.CompactTextString(m) }
+func (*CreateClientResp) ProtoMessage()    {}
+func (*CreateClientResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{2}
+}
+
+func (m *CreateClientResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CreateClientResp.Unmarshal(m, b)
+}
+func (m *CreateClientResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CreateClientResp.Marshal(b, m, deterministic)
+}
+func (m *CreateClientResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CreateClientResp.Merge(m, src)
+}
+func (m *CreateClientResp) XXX_Size() int {
+	return xxx_messageInfo_CreateClientResp.Size(m)
+}
+func (m *CreateClientResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_CreateClientResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CreateClientResp proto.InternalMessageInfo
+
+func (m *CreateClientResp) GetAlreadyExists() bool {
+	if m != nil {
+		return m.AlreadyExists
+	}
+	return false
+}
+
+func (m *CreateClientResp) GetClient() *Client {
+	if m != nil {
+		return m.Client
+	}
+	return nil
+}
+
+// DeleteClientReq is a request to delete a client.
+type DeleteClientReq struct {
+	// The ID of the client.
+	Id                   string   `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *DeleteClientReq) Reset()         { *m = DeleteClientReq{} }
+func (m *DeleteClientReq) String() string { return proto.CompactTextString(m) }
+func (*DeleteClientReq) ProtoMessage()    {}
+func (*DeleteClientReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{3}
+}
+
+func (m *DeleteClientReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_DeleteClientReq.Unmarshal(m, b)
+}
+func (m *DeleteClientReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_DeleteClientReq.Marshal(b, m, deterministic)
+}
+func (m *DeleteClientReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DeleteClientReq.Merge(m, src)
+}
+func (m *DeleteClientReq) XXX_Size() int {
+	return xxx_messageInfo_DeleteClientReq.Size(m)
+}
+func (m *DeleteClientReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_DeleteClientReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DeleteClientReq proto.InternalMessageInfo
+
+func (m *DeleteClientReq) GetId() string {
+	if m != nil {
+		return m.Id
+	}
+	return ""
+}
+
+// DeleteClientResp determines if the client is deleted successfully.
+type DeleteClientResp struct {
+	NotFound             bool     `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *DeleteClientResp) Reset()         { *m = DeleteClientResp{} }
+func (m *DeleteClientResp) String() string { return proto.CompactTextString(m) }
+func (*DeleteClientResp) ProtoMessage()    {}
+func (*DeleteClientResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{4}
+}
+
+func (m *DeleteClientResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_DeleteClientResp.Unmarshal(m, b)
+}
+func (m *DeleteClientResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_DeleteClientResp.Marshal(b, m, deterministic)
+}
+func (m *DeleteClientResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DeleteClientResp.Merge(m, src)
+}
+func (m *DeleteClientResp) XXX_Size() int {
+	return xxx_messageInfo_DeleteClientResp.Size(m)
+}
+func (m *DeleteClientResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_DeleteClientResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DeleteClientResp proto.InternalMessageInfo
+
+func (m *DeleteClientResp) GetNotFound() bool {
+	if m != nil {
+		return m.NotFound
+	}
+	return false
+}
+
+// UpdateClientReq is a request to update an exisitng client.
+type UpdateClientReq struct {
+	Id                   string   `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	RedirectUris         []string `protobuf:"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3" json:"redirect_uris,omitempty"`
+	TrustedPeers         []string `protobuf:"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3" json:"trusted_peers,omitempty"`
+	Name                 string   `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`
+	LogoUrl              string   `protobuf:"bytes,5,opt,name=logo_url,json=logoUrl,proto3" json:"logo_url,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *UpdateClientReq) Reset()         { *m = UpdateClientReq{} }
+func (m *UpdateClientReq) String() string { return proto.CompactTextString(m) }
+func (*UpdateClientReq) ProtoMessage()    {}
+func (*UpdateClientReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{5}
+}
+
+func (m *UpdateClientReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_UpdateClientReq.Unmarshal(m, b)
+}
+func (m *UpdateClientReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_UpdateClientReq.Marshal(b, m, deterministic)
+}
+func (m *UpdateClientReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_UpdateClientReq.Merge(m, src)
+}
+func (m *UpdateClientReq) XXX_Size() int {
+	return xxx_messageInfo_UpdateClientReq.Size(m)
+}
+func (m *UpdateClientReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_UpdateClientReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UpdateClientReq proto.InternalMessageInfo
+
+func (m *UpdateClientReq) GetId() string {
+	if m != nil {
+		return m.Id
+	}
+	return ""
+}
+
+func (m *UpdateClientReq) GetRedirectUris() []string {
+	if m != nil {
+		return m.RedirectUris
+	}
+	return nil
+}
+
+func (m *UpdateClientReq) GetTrustedPeers() []string {
+	if m != nil {
+		return m.TrustedPeers
+	}
+	return nil
+}
+
+func (m *UpdateClientReq) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
+func (m *UpdateClientReq) GetLogoUrl() string {
+	if m != nil {
+		return m.LogoUrl
+	}
+	return ""
+}
+
+// UpdateClientResp returns the reponse form updating a client.
+type UpdateClientResp struct {
+	NotFound             bool     `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *UpdateClientResp) Reset()         { *m = UpdateClientResp{} }
+func (m *UpdateClientResp) String() string { return proto.CompactTextString(m) }
+func (*UpdateClientResp) ProtoMessage()    {}
+func (*UpdateClientResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{6}
+}
+
+func (m *UpdateClientResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_UpdateClientResp.Unmarshal(m, b)
+}
+func (m *UpdateClientResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_UpdateClientResp.Marshal(b, m, deterministic)
+}
+func (m *UpdateClientResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_UpdateClientResp.Merge(m, src)
+}
+func (m *UpdateClientResp) XXX_Size() int {
+	return xxx_messageInfo_UpdateClientResp.Size(m)
+}
+func (m *UpdateClientResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_UpdateClientResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UpdateClientResp proto.InternalMessageInfo
+
+func (m *UpdateClientResp) GetNotFound() bool {
+	if m != nil {
+		return m.NotFound
+	}
+	return false
+}
+
+// Password is an email for password mapping managed by the storage.
+type Password struct {
+	Email string `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"`
+	// Currently we do not accept plain text passwords. Could be an option in the future.
+	Hash                 []byte   `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"`
+	Username             string   `protobuf:"bytes,3,opt,name=username,proto3" json:"username,omitempty"`
+	UserId               string   `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Password) Reset()         { *m = Password{} }
+func (m *Password) String() string { return proto.CompactTextString(m) }
+func (*Password) ProtoMessage()    {}
+func (*Password) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{7}
+}
+
+func (m *Password) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_Password.Unmarshal(m, b)
+}
+func (m *Password) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_Password.Marshal(b, m, deterministic)
+}
+func (m *Password) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Password.Merge(m, src)
+}
+func (m *Password) XXX_Size() int {
+	return xxx_messageInfo_Password.Size(m)
+}
+func (m *Password) XXX_DiscardUnknown() {
+	xxx_messageInfo_Password.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Password proto.InternalMessageInfo
+
+func (m *Password) GetEmail() string {
+	if m != nil {
+		return m.Email
+	}
+	return ""
+}
+
+func (m *Password) GetHash() []byte {
+	if m != nil {
+		return m.Hash
+	}
+	return nil
+}
+
+func (m *Password) GetUsername() string {
+	if m != nil {
+		return m.Username
+	}
+	return ""
+}
+
+func (m *Password) GetUserId() string {
+	if m != nil {
+		return m.UserId
+	}
+	return ""
+}
+
+// CreatePasswordReq is a request to make a password.
+type CreatePasswordReq struct {
+	Password             *Password `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}  `json:"-"`
+	XXX_unrecognized     []byte    `json:"-"`
+	XXX_sizecache        int32     `json:"-"`
+}
+
+func (m *CreatePasswordReq) Reset()         { *m = CreatePasswordReq{} }
+func (m *CreatePasswordReq) String() string { return proto.CompactTextString(m) }
+func (*CreatePasswordReq) ProtoMessage()    {}
+func (*CreatePasswordReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{8}
+}
+
+func (m *CreatePasswordReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CreatePasswordReq.Unmarshal(m, b)
+}
+func (m *CreatePasswordReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CreatePasswordReq.Marshal(b, m, deterministic)
+}
+func (m *CreatePasswordReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CreatePasswordReq.Merge(m, src)
+}
+func (m *CreatePasswordReq) XXX_Size() int {
+	return xxx_messageInfo_CreatePasswordReq.Size(m)
+}
+func (m *CreatePasswordReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_CreatePasswordReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CreatePasswordReq proto.InternalMessageInfo
+
+func (m *CreatePasswordReq) GetPassword() *Password {
+	if m != nil {
+		return m.Password
+	}
+	return nil
+}
+
+// CreatePasswordResp returns the response from creating a password.
+type CreatePasswordResp struct {
+	AlreadyExists        bool     `protobuf:"varint,1,opt,name=already_exists,json=alreadyExists,proto3" json:"already_exists,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *CreatePasswordResp) Reset()         { *m = CreatePasswordResp{} }
+func (m *CreatePasswordResp) String() string { return proto.CompactTextString(m) }
+func (*CreatePasswordResp) ProtoMessage()    {}
+func (*CreatePasswordResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{9}
+}
+
+func (m *CreatePasswordResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_CreatePasswordResp.Unmarshal(m, b)
+}
+func (m *CreatePasswordResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_CreatePasswordResp.Marshal(b, m, deterministic)
+}
+func (m *CreatePasswordResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_CreatePasswordResp.Merge(m, src)
+}
+func (m *CreatePasswordResp) XXX_Size() int {
+	return xxx_messageInfo_CreatePasswordResp.Size(m)
+}
+func (m *CreatePasswordResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_CreatePasswordResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_CreatePasswordResp proto.InternalMessageInfo
+
+func (m *CreatePasswordResp) GetAlreadyExists() bool {
+	if m != nil {
+		return m.AlreadyExists
+	}
+	return false
+}
+
+// UpdatePasswordReq is a request to modify an existing password.
+type UpdatePasswordReq struct {
+	// The email used to lookup the password. This field cannot be modified
+	Email                string   `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"`
+	NewHash              []byte   `protobuf:"bytes,2,opt,name=new_hash,json=newHash,proto3" json:"new_hash,omitempty"`
+	NewUsername          string   `protobuf:"bytes,3,opt,name=new_username,json=newUsername,proto3" json:"new_username,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *UpdatePasswordReq) Reset()         { *m = UpdatePasswordReq{} }
+func (m *UpdatePasswordReq) String() string { return proto.CompactTextString(m) }
+func (*UpdatePasswordReq) ProtoMessage()    {}
+func (*UpdatePasswordReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{10}
+}
+
+func (m *UpdatePasswordReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_UpdatePasswordReq.Unmarshal(m, b)
+}
+func (m *UpdatePasswordReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_UpdatePasswordReq.Marshal(b, m, deterministic)
+}
+func (m *UpdatePasswordReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_UpdatePasswordReq.Merge(m, src)
+}
+func (m *UpdatePasswordReq) XXX_Size() int {
+	return xxx_messageInfo_UpdatePasswordReq.Size(m)
+}
+func (m *UpdatePasswordReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_UpdatePasswordReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UpdatePasswordReq proto.InternalMessageInfo
+
+func (m *UpdatePasswordReq) GetEmail() string {
+	if m != nil {
+		return m.Email
+	}
+	return ""
+}
+
+func (m *UpdatePasswordReq) GetNewHash() []byte {
+	if m != nil {
+		return m.NewHash
+	}
+	return nil
+}
+
+func (m *UpdatePasswordReq) GetNewUsername() string {
+	if m != nil {
+		return m.NewUsername
+	}
+	return ""
+}
+
+// UpdatePasswordResp returns the response from modifying an existing password.
+type UpdatePasswordResp struct {
+	NotFound             bool     `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *UpdatePasswordResp) Reset()         { *m = UpdatePasswordResp{} }
+func (m *UpdatePasswordResp) String() string { return proto.CompactTextString(m) }
+func (*UpdatePasswordResp) ProtoMessage()    {}
+func (*UpdatePasswordResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{11}
+}
+
+func (m *UpdatePasswordResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_UpdatePasswordResp.Unmarshal(m, b)
+}
+func (m *UpdatePasswordResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_UpdatePasswordResp.Marshal(b, m, deterministic)
+}
+func (m *UpdatePasswordResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_UpdatePasswordResp.Merge(m, src)
+}
+func (m *UpdatePasswordResp) XXX_Size() int {
+	return xxx_messageInfo_UpdatePasswordResp.Size(m)
+}
+func (m *UpdatePasswordResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_UpdatePasswordResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_UpdatePasswordResp proto.InternalMessageInfo
+
+func (m *UpdatePasswordResp) GetNotFound() bool {
+	if m != nil {
+		return m.NotFound
+	}
+	return false
+}
+
+// DeletePasswordReq is a request to delete a password.
+type DeletePasswordReq struct {
+	Email                string   `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *DeletePasswordReq) Reset()         { *m = DeletePasswordReq{} }
+func (m *DeletePasswordReq) String() string { return proto.CompactTextString(m) }
+func (*DeletePasswordReq) ProtoMessage()    {}
+func (*DeletePasswordReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{12}
+}
+
+func (m *DeletePasswordReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_DeletePasswordReq.Unmarshal(m, b)
+}
+func (m *DeletePasswordReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_DeletePasswordReq.Marshal(b, m, deterministic)
+}
+func (m *DeletePasswordReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DeletePasswordReq.Merge(m, src)
+}
+func (m *DeletePasswordReq) XXX_Size() int {
+	return xxx_messageInfo_DeletePasswordReq.Size(m)
+}
+func (m *DeletePasswordReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_DeletePasswordReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DeletePasswordReq proto.InternalMessageInfo
+
+func (m *DeletePasswordReq) GetEmail() string {
+	if m != nil {
+		return m.Email
+	}
+	return ""
+}
+
+// DeletePasswordResp returns the response from deleting a password.
+type DeletePasswordResp struct {
+	NotFound             bool     `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *DeletePasswordResp) Reset()         { *m = DeletePasswordResp{} }
+func (m *DeletePasswordResp) String() string { return proto.CompactTextString(m) }
+func (*DeletePasswordResp) ProtoMessage()    {}
+func (*DeletePasswordResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{13}
+}
+
+func (m *DeletePasswordResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_DeletePasswordResp.Unmarshal(m, b)
+}
+func (m *DeletePasswordResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_DeletePasswordResp.Marshal(b, m, deterministic)
+}
+func (m *DeletePasswordResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_DeletePasswordResp.Merge(m, src)
+}
+func (m *DeletePasswordResp) XXX_Size() int {
+	return xxx_messageInfo_DeletePasswordResp.Size(m)
+}
+func (m *DeletePasswordResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_DeletePasswordResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_DeletePasswordResp proto.InternalMessageInfo
+
+func (m *DeletePasswordResp) GetNotFound() bool {
+	if m != nil {
+		return m.NotFound
+	}
+	return false
+}
+
+// ListPasswordReq is a request to enumerate passwords.
+type ListPasswordReq struct {
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ListPasswordReq) Reset()         { *m = ListPasswordReq{} }
+func (m *ListPasswordReq) String() string { return proto.CompactTextString(m) }
+func (*ListPasswordReq) ProtoMessage()    {}
+func (*ListPasswordReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{14}
+}
+
+func (m *ListPasswordReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ListPasswordReq.Unmarshal(m, b)
+}
+func (m *ListPasswordReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ListPasswordReq.Marshal(b, m, deterministic)
+}
+func (m *ListPasswordReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListPasswordReq.Merge(m, src)
+}
+func (m *ListPasswordReq) XXX_Size() int {
+	return xxx_messageInfo_ListPasswordReq.Size(m)
+}
+func (m *ListPasswordReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListPasswordReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListPasswordReq proto.InternalMessageInfo
+
+// ListPasswordResp returns a list of passwords.
+type ListPasswordResp struct {
+	Passwords            []*Password `protobuf:"bytes,1,rep,name=passwords,proto3" json:"passwords,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *ListPasswordResp) Reset()         { *m = ListPasswordResp{} }
+func (m *ListPasswordResp) String() string { return proto.CompactTextString(m) }
+func (*ListPasswordResp) ProtoMessage()    {}
+func (*ListPasswordResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{15}
+}
+
+func (m *ListPasswordResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ListPasswordResp.Unmarshal(m, b)
+}
+func (m *ListPasswordResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ListPasswordResp.Marshal(b, m, deterministic)
+}
+func (m *ListPasswordResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListPasswordResp.Merge(m, src)
+}
+func (m *ListPasswordResp) XXX_Size() int {
+	return xxx_messageInfo_ListPasswordResp.Size(m)
+}
+func (m *ListPasswordResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListPasswordResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListPasswordResp proto.InternalMessageInfo
+
+func (m *ListPasswordResp) GetPasswords() []*Password {
+	if m != nil {
+		return m.Passwords
+	}
+	return nil
+}
+
+// VersionReq is a request to fetch version info.
+type VersionReq struct {
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *VersionReq) Reset()         { *m = VersionReq{} }
+func (m *VersionReq) String() string { return proto.CompactTextString(m) }
+func (*VersionReq) ProtoMessage()    {}
+func (*VersionReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{16}
+}
+
+func (m *VersionReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_VersionReq.Unmarshal(m, b)
+}
+func (m *VersionReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_VersionReq.Marshal(b, m, deterministic)
+}
+func (m *VersionReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_VersionReq.Merge(m, src)
+}
+func (m *VersionReq) XXX_Size() int {
+	return xxx_messageInfo_VersionReq.Size(m)
+}
+func (m *VersionReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_VersionReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_VersionReq proto.InternalMessageInfo
+
+// VersionResp holds the version info of components.
+type VersionResp struct {
+	// Semantic version of the server.
+	Server string `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
+	// Numeric version of the API. It increases everytime a new call is added to the API.
+	// Clients should use this info to determine if the server supports specific features.
+	Api                  int32    `protobuf:"varint,2,opt,name=api,proto3" json:"api,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *VersionResp) Reset()         { *m = VersionResp{} }
+func (m *VersionResp) String() string { return proto.CompactTextString(m) }
+func (*VersionResp) ProtoMessage()    {}
+func (*VersionResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{17}
+}
+
+func (m *VersionResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_VersionResp.Unmarshal(m, b)
+}
+func (m *VersionResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_VersionResp.Marshal(b, m, deterministic)
+}
+func (m *VersionResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_VersionResp.Merge(m, src)
+}
+func (m *VersionResp) XXX_Size() int {
+	return xxx_messageInfo_VersionResp.Size(m)
+}
+func (m *VersionResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_VersionResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_VersionResp proto.InternalMessageInfo
+
+func (m *VersionResp) GetServer() string {
+	if m != nil {
+		return m.Server
+	}
+	return ""
+}
+
+func (m *VersionResp) GetApi() int32 {
+	if m != nil {
+		return m.Api
+	}
+	return 0
+}
+
+// RefreshTokenRef contains the metadata for a refresh token that is managed by the storage.
+type RefreshTokenRef struct {
+	// ID of the refresh token.
+	Id                   string   `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+	ClientId             string   `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
+	CreatedAt            int64    `protobuf:"varint,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+	LastUsed             int64    `protobuf:"varint,6,opt,name=last_used,json=lastUsed,proto3" json:"last_used,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *RefreshTokenRef) Reset()         { *m = RefreshTokenRef{} }
+func (m *RefreshTokenRef) String() string { return proto.CompactTextString(m) }
+func (*RefreshTokenRef) ProtoMessage()    {}
+func (*RefreshTokenRef) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{18}
+}
+
+func (m *RefreshTokenRef) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_RefreshTokenRef.Unmarshal(m, b)
+}
+func (m *RefreshTokenRef) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_RefreshTokenRef.Marshal(b, m, deterministic)
+}
+func (m *RefreshTokenRef) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RefreshTokenRef.Merge(m, src)
+}
+func (m *RefreshTokenRef) XXX_Size() int {
+	return xxx_messageInfo_RefreshTokenRef.Size(m)
+}
+func (m *RefreshTokenRef) XXX_DiscardUnknown() {
+	xxx_messageInfo_RefreshTokenRef.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RefreshTokenRef proto.InternalMessageInfo
+
+func (m *RefreshTokenRef) GetId() string {
+	if m != nil {
+		return m.Id
+	}
+	return ""
+}
+
+func (m *RefreshTokenRef) GetClientId() string {
+	if m != nil {
+		return m.ClientId
+	}
+	return ""
+}
+
+func (m *RefreshTokenRef) GetCreatedAt() int64 {
+	if m != nil {
+		return m.CreatedAt
+	}
+	return 0
+}
+
+func (m *RefreshTokenRef) GetLastUsed() int64 {
+	if m != nil {
+		return m.LastUsed
+	}
+	return 0
+}
+
+// ListRefreshReq is a request to enumerate the refresh tokens of a user.
+type ListRefreshReq struct {
+	// The "sub" claim returned in the ID Token.
+	UserId               string   `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ListRefreshReq) Reset()         { *m = ListRefreshReq{} }
+func (m *ListRefreshReq) String() string { return proto.CompactTextString(m) }
+func (*ListRefreshReq) ProtoMessage()    {}
+func (*ListRefreshReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{19}
+}
+
+func (m *ListRefreshReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ListRefreshReq.Unmarshal(m, b)
+}
+func (m *ListRefreshReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ListRefreshReq.Marshal(b, m, deterministic)
+}
+func (m *ListRefreshReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListRefreshReq.Merge(m, src)
+}
+func (m *ListRefreshReq) XXX_Size() int {
+	return xxx_messageInfo_ListRefreshReq.Size(m)
+}
+func (m *ListRefreshReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListRefreshReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListRefreshReq proto.InternalMessageInfo
+
+func (m *ListRefreshReq) GetUserId() string {
+	if m != nil {
+		return m.UserId
+	}
+	return ""
+}
+
+// ListRefreshResp returns a list of refresh tokens for a user.
+type ListRefreshResp struct {
+	RefreshTokens        []*RefreshTokenRef `protobuf:"bytes,1,rep,name=refresh_tokens,json=refreshTokens,proto3" json:"refresh_tokens,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}           `json:"-"`
+	XXX_unrecognized     []byte             `json:"-"`
+	XXX_sizecache        int32              `json:"-"`
+}
+
+func (m *ListRefreshResp) Reset()         { *m = ListRefreshResp{} }
+func (m *ListRefreshResp) String() string { return proto.CompactTextString(m) }
+func (*ListRefreshResp) ProtoMessage()    {}
+func (*ListRefreshResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{20}
+}
+
+func (m *ListRefreshResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_ListRefreshResp.Unmarshal(m, b)
+}
+func (m *ListRefreshResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_ListRefreshResp.Marshal(b, m, deterministic)
+}
+func (m *ListRefreshResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListRefreshResp.Merge(m, src)
+}
+func (m *ListRefreshResp) XXX_Size() int {
+	return xxx_messageInfo_ListRefreshResp.Size(m)
+}
+func (m *ListRefreshResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListRefreshResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListRefreshResp proto.InternalMessageInfo
+
+func (m *ListRefreshResp) GetRefreshTokens() []*RefreshTokenRef {
+	if m != nil {
+		return m.RefreshTokens
+	}
+	return nil
+}
+
+// RevokeRefreshReq is a request to revoke the refresh token of the user-client pair.
+type RevokeRefreshReq struct {
+	// The "sub" claim returned in the ID Token.
+	UserId               string   `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+	ClientId             string   `protobuf:"bytes,2,opt,name=client_id,json=clientId,proto3" json:"client_id,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *RevokeRefreshReq) Reset()         { *m = RevokeRefreshReq{} }
+func (m *RevokeRefreshReq) String() string { return proto.CompactTextString(m) }
+func (*RevokeRefreshReq) ProtoMessage()    {}
+func (*RevokeRefreshReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{21}
+}
+
+func (m *RevokeRefreshReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_RevokeRefreshReq.Unmarshal(m, b)
+}
+func (m *RevokeRefreshReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_RevokeRefreshReq.Marshal(b, m, deterministic)
+}
+func (m *RevokeRefreshReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RevokeRefreshReq.Merge(m, src)
+}
+func (m *RevokeRefreshReq) XXX_Size() int {
+	return xxx_messageInfo_RevokeRefreshReq.Size(m)
+}
+func (m *RevokeRefreshReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_RevokeRefreshReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RevokeRefreshReq proto.InternalMessageInfo
+
+func (m *RevokeRefreshReq) GetUserId() string {
+	if m != nil {
+		return m.UserId
+	}
+	return ""
+}
+
+func (m *RevokeRefreshReq) GetClientId() string {
+	if m != nil {
+		return m.ClientId
+	}
+	return ""
+}
+
+// RevokeRefreshResp determines if the refresh token is revoked successfully.
+type RevokeRefreshResp struct {
+	// Set to true is refresh token was not found and token could not be revoked.
+	NotFound             bool     `protobuf:"varint,1,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *RevokeRefreshResp) Reset()         { *m = RevokeRefreshResp{} }
+func (m *RevokeRefreshResp) String() string { return proto.CompactTextString(m) }
+func (*RevokeRefreshResp) ProtoMessage()    {}
+func (*RevokeRefreshResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{22}
+}
+
+func (m *RevokeRefreshResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_RevokeRefreshResp.Unmarshal(m, b)
+}
+func (m *RevokeRefreshResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_RevokeRefreshResp.Marshal(b, m, deterministic)
+}
+func (m *RevokeRefreshResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RevokeRefreshResp.Merge(m, src)
+}
+func (m *RevokeRefreshResp) XXX_Size() int {
+	return xxx_messageInfo_RevokeRefreshResp.Size(m)
+}
+func (m *RevokeRefreshResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_RevokeRefreshResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RevokeRefreshResp proto.InternalMessageInfo
+
+func (m *RevokeRefreshResp) GetNotFound() bool {
+	if m != nil {
+		return m.NotFound
+	}
+	return false
+}
+
+type VerifyPasswordReq struct {
+	Email                string   `protobuf:"bytes,1,opt,name=email,proto3" json:"email,omitempty"`
+	Password             string   `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *VerifyPasswordReq) Reset()         { *m = VerifyPasswordReq{} }
+func (m *VerifyPasswordReq) String() string { return proto.CompactTextString(m) }
+func (*VerifyPasswordReq) ProtoMessage()    {}
+func (*VerifyPasswordReq) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{23}
+}
+
+func (m *VerifyPasswordReq) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_VerifyPasswordReq.Unmarshal(m, b)
+}
+func (m *VerifyPasswordReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_VerifyPasswordReq.Marshal(b, m, deterministic)
+}
+func (m *VerifyPasswordReq) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_VerifyPasswordReq.Merge(m, src)
+}
+func (m *VerifyPasswordReq) XXX_Size() int {
+	return xxx_messageInfo_VerifyPasswordReq.Size(m)
+}
+func (m *VerifyPasswordReq) XXX_DiscardUnknown() {
+	xxx_messageInfo_VerifyPasswordReq.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_VerifyPasswordReq proto.InternalMessageInfo
+
+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,proto3" json:"verified,omitempty"`
+	NotFound             bool     `protobuf:"varint,2,opt,name=not_found,json=notFound,proto3" json:"not_found,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *VerifyPasswordResp) Reset()         { *m = VerifyPasswordResp{} }
+func (m *VerifyPasswordResp) String() string { return proto.CompactTextString(m) }
+func (*VerifyPasswordResp) ProtoMessage()    {}
+func (*VerifyPasswordResp) Descriptor() ([]byte, []int) {
+	return fileDescriptor_14cbb315f08d2e3f, []int{24}
+}
+
+func (m *VerifyPasswordResp) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_VerifyPasswordResp.Unmarshal(m, b)
+}
+func (m *VerifyPasswordResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_VerifyPasswordResp.Marshal(b, m, deterministic)
+}
+func (m *VerifyPasswordResp) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_VerifyPasswordResp.Merge(m, src)
+}
+func (m *VerifyPasswordResp) XXX_Size() int {
+	return xxx_messageInfo_VerifyPasswordResp.Size(m)
+}
+func (m *VerifyPasswordResp) XXX_DiscardUnknown() {
+	xxx_messageInfo_VerifyPasswordResp.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_VerifyPasswordResp proto.InternalMessageInfo
+
+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")
+	proto.RegisterType((*CreateClientResp)(nil), "api.CreateClientResp")
+	proto.RegisterType((*DeleteClientReq)(nil), "api.DeleteClientReq")
+	proto.RegisterType((*DeleteClientResp)(nil), "api.DeleteClientResp")
+	proto.RegisterType((*UpdateClientReq)(nil), "api.UpdateClientReq")
+	proto.RegisterType((*UpdateClientResp)(nil), "api.UpdateClientResp")
+	proto.RegisterType((*Password)(nil), "api.Password")
+	proto.RegisterType((*CreatePasswordReq)(nil), "api.CreatePasswordReq")
+	proto.RegisterType((*CreatePasswordResp)(nil), "api.CreatePasswordResp")
+	proto.RegisterType((*UpdatePasswordReq)(nil), "api.UpdatePasswordReq")
+	proto.RegisterType((*UpdatePasswordResp)(nil), "api.UpdatePasswordResp")
+	proto.RegisterType((*DeletePasswordReq)(nil), "api.DeletePasswordReq")
+	proto.RegisterType((*DeletePasswordResp)(nil), "api.DeletePasswordResp")
+	proto.RegisterType((*ListPasswordReq)(nil), "api.ListPasswordReq")
+	proto.RegisterType((*ListPasswordResp)(nil), "api.ListPasswordResp")
+	proto.RegisterType((*VersionReq)(nil), "api.VersionReq")
+	proto.RegisterType((*VersionResp)(nil), "api.VersionResp")
+	proto.RegisterType((*RefreshTokenRef)(nil), "api.RefreshTokenRef")
+	proto.RegisterType((*ListRefreshReq)(nil), "api.ListRefreshReq")
+	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")
+}
+
+func init() { proto.RegisterFile("api/v2/api.proto", fileDescriptor_14cbb315f08d2e3f) }
+
+var fileDescriptor_14cbb315f08d2e3f = []byte{
+	// 908 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, 0x91, 0x8f, 0x6f, 0x32, 0xe7, 0xa6, 0xae, 0x8b, 0x01, 0x29, 0x8b, 0x01,
+	0x29, 0x06, 0xd8, 0x6b, 0x06, 0x6c, 0xc0, 0x8a, 0x75, 0x97, 0xb4, 0x5b, 0x0b, 0x6c, 0x43, 0x21,
+	0xcc, 0xfd, 0x39, 0x41, 0xb5, 0x8e, 0x1b, 0xa2, 0x8a, 0xa4, 0x91, 0xb4, 0x9d, 0xee, 0x51, 0xf6,
+	0x36, 0xfb, 0xb5, 0xd7, 0x2a, 0x48, 0x51, 0x8a, 0x2e, 0x6e, 0x9d, 0x7f, 0x3e, 0x9f, 0x78, 0x6e,
+	0xdf, 0xe1, 0xf9, 0x68, 0x70, 0xfc, 0x84, 0x2d, 0xb6, 0xe7, 0x0b, 0x3f, 0x61, 0xf3, 0x84, 0xc7,
+	0x32, 0x26, 0x96, 0x9f, 0x30, 0xfa, 0x5f, 0x13, 0x3a, 0x17, 0x21, 0xc3, 0x48, 0x92, 0x21, 0xb4,
+	0x58, 0x30, 0x6d, 0x9e, 0x36, 0xcf, 0xba, 0x6e, 0x8b, 0x05, 0xe4, 0x04, 0x3a, 0x02, 0x57, 0x1c,
+	0xe5, 0xb4, 0xa5, 0x31, 0x63, 0x91, 0x87, 0x30, 0xe0, 0x18, 0x30, 0x8e, 0x2b, 0xe9, 0x6d, 0x38,
+	0x13, 0x53, 0xeb, 0xd4, 0x3a, 0xeb, 0xba, 0xfd, 0x0c, 0x5c, 0x72, 0x26, 0xd4, 0x21, 0xc9, 0x37,
+	0x42, 0x62, 0xe0, 0x25, 0x88, 0x5c, 0x4c, 0x8f, 0xd2, 0x43, 0x06, 0x7c, 0xa5, 0x30, 0x95, 0x21,
+	0xd9, 0xbc, 0x09, 0xd9, 0x6a, 0xda, 0x3e, 0x6d, 0x9e, 0xd9, 0xae, 0xb1, 0x08, 0x81, 0xa3, 0xc8,
+	0xbf, 0xc2, 0x69, 0x47, 0xe7, 0xd5, 0xbf, 0xc9, 0x3d, 0xb0, 0xc3, 0xf8, 0x6d, 0xec, 0x6d, 0x78,
+	0x38, 0x3d, 0xd6, 0xf8, 0xb1, 0xb2, 0x97, 0x3c, 0xa4, 0xdf, 0xc0, 0xe8, 0x82, 0xa3, 0x2f, 0x31,
+	0x6d, 0xc4, 0xc5, 0xbf, 0xc9, 0x43, 0xe8, 0xac, 0xb4, 0xa1, 0xfb, 0xe9, 0x9d, 0xf7, 0xe6, 0xaa,
+	0x6f, 0xf3, 0xdd, 0x7c, 0xa2, 0x7f, 0x81, 0x53, 0xf6, 0x13, 0x09, 0xf9, 0x02, 0x86, 0x7e, 0xc8,
+	0xd1, 0x0f, 0xde, 0x7b, 0x78, 0xcd, 0x84, 0x14, 0x3a, 0x80, 0xed, 0x0e, 0x0c, 0xfa, 0x5c, 0x83,
+	0x85, 0xf8, 0xad, 0x8f, 0xc7, 0x7f, 0x00, 0xa3, 0x67, 0x18, 0x62, 0xb1, 0xae, 0x0a, 0xc7, 0x74,
+	0x01, 0x4e, 0xf9, 0x88, 0x48, 0xc8, 0x7d, 0xe8, 0x46, 0xb1, 0xf4, 0xd6, 0xf1, 0x26, 0x0a, 0x4c,
+	0x76, 0x3b, 0x8a, 0xe5, 0x2f, 0xca, 0xa6, 0xff, 0x36, 0x61, 0xb4, 0x4c, 0x02, 0xff, 0x13, 0x41,
+	0xeb, 0x03, 0x6a, 0xdd, 0x66, 0x40, 0xd6, 0x9e, 0x01, 0x65, 0x83, 0x38, 0xfa, 0xc8, 0x20, 0xda,
+	0xe5, 0x41, 0x2c, 0xc0, 0x29, 0xd7, 0x76, 0xa8, 0x1b, 0x06, 0xf6, 0x2b, 0x5f, 0x88, 0x5d, 0xcc,
+	0x03, 0x32, 0x81, 0x36, 0x5e, 0xf9, 0x2c, 0x34, 0x8d, 0xa4, 0x86, 0xaa, 0xe0, 0xd2, 0x17, 0x97,
+	0x9a, 0xe6, 0xbe, 0xab, 0x7f, 0x93, 0x19, 0xd8, 0x1b, 0x81, 0x5c, 0x57, 0x66, 0xe9, 0xc3, 0xb9,
+	0x4d, 0xee, 0xc2, 0xb1, 0xfa, 0xed, 0xb1, 0xc0, 0x14, 0xdd, 0x51, 0xe6, 0xcb, 0x80, 0x3e, 0x85,
+	0x71, 0x3a, 0xec, 0x2c, 0xa1, 0x62, 0xee, 0x11, 0xd8, 0x89, 0x31, 0xcd, 0x45, 0x19, 0xe8, 0x41,
+	0xe6, 0x67, 0xf2, 0xcf, 0xf4, 0x09, 0x90, 0xaa, 0xff, 0xad, 0xaf, 0x0b, 0x7d, 0x0b, 0xe3, 0x94,
+	0x98, 0x62, 0xf2, 0xfd, 0x0d, 0xdf, 0x03, 0x3b, 0xc2, 0x9d, 0x57, 0x68, 0xfa, 0x38, 0xc2, 0xdd,
+	0x0b, 0xd5, 0xf7, 0x03, 0xe8, 0xab, 0x4f, 0x95, 0xde, 0x7b, 0x11, 0xee, 0x96, 0x06, 0xa2, 0x8f,
+	0x81, 0x54, 0x13, 0x1d, 0x9a, 0xc1, 0x23, 0x18, 0xa7, 0x57, 0xf0, 0x60, 0x6d, 0x2a, 0x7a, 0xf5,
+	0xe8, 0xa1, 0xe8, 0x63, 0x18, 0xfd, 0xc6, 0x84, 0x2c, 0xc4, 0xa6, 0x3f, 0x80, 0x53, 0x86, 0x44,
+	0x42, 0xbe, 0x84, 0x6e, 0xc6, 0xb4, 0xa2, 0xd0, 0xaa, 0x4f, 0xe2, 0xe6, 0x3b, 0xed, 0x03, 0xbc,
+	0x46, 0x2e, 0x58, 0x1c, 0xa9, 0x70, 0xdf, 0x42, 0x2f, 0xb7, 0x44, 0x92, 0xaa, 0x16, 0xdf, 0x22,
+	0x37, 0xa5, 0x1b, 0x8b, 0x38, 0xa0, 0xf4, 0x4e, 0x53, 0xda, 0x76, 0xb5, 0xf4, 0xfd, 0x03, 0x23,
+	0x17, 0xd7, 0x1c, 0xc5, 0xe5, 0x9f, 0xf1, 0x3b, 0x8c, 0x5c, 0x5c, 0xd7, 0x36, 0xe9, 0x3e, 0x74,
+	0xd3, 0x5d, 0x56, 0xf7, 0x29, 0x55, 0x41, 0x3b, 0x05, 0x5e, 0x06, 0xe4, 0x73, 0x80, 0x95, 0xbe,
+	0x11, 0x81, 0xe7, 0x4b, 0xbd, 0x0a, 0x96, 0xdb, 0x35, 0xc8, 0x4f, 0x52, 0xf9, 0x86, 0xbe, 0x90,
+	0x6a, 0x5c, 0x81, 0x56, 0x32, 0xcb, 0xb5, 0x15, 0xb0, 0x14, 0xa8, 0x48, 0x1f, 0x2a, 0x0e, 0x4c,
+	0x7e, 0xc5, 0x78, 0xe1, 0xe2, 0x36, 0x4b, 0x17, 0xf7, 0x8f, 0x94, 0xc1, 0xfc, 0xa8, 0x48, 0xc8,
+	0x13, 0x18, 0xf2, 0xd4, 0xf4, 0xa4, 0x2a, 0x3d, 0xa3, 0x6c, 0xa2, 0x29, 0xab, 0x34, 0xe5, 0x0e,
+	0x78, 0x01, 0x10, 0xf4, 0x05, 0x38, 0x2e, 0x6e, 0xe3, 0x77, 0x78, 0x8b, 0xe4, 0x9f, 0x24, 0x80,
+	0x7e, 0x05, 0xe3, 0x4a, 0xa4, 0x43, 0xb7, 0xe1, 0x39, 0x8c, 0x5f, 0x23, 0x67, 0xeb, 0xf7, 0x87,
+	0xf7, 0x60, 0x56, 0x58, 0x4d, 0x93, 0x38, 0xdf, 0xc5, 0xdf, 0x81, 0x54, 0xc3, 0x88, 0x44, 0x79,
+	0x6c, 0x15, 0xca, 0x30, 0x4f, 0x9c, 0xd9, 0xe5, 0xaa, 0x5a, 0xe5, 0xaa, 0xce, 0xff, 0x6f, 0x83,
+	0xf5, 0x0c, 0xaf, 0xc9, 0xf7, 0xd0, 0x2f, 0xbe, 0x07, 0x24, 0xa5, 0xb3, 0xf2, 0xb4, 0xcc, 0xee,
+	0xec, 0x41, 0x45, 0x42, 0x1b, 0xca, 0xbd, 0xa8, 0x7e, 0xc6, 0xbd, 0x22, 0xd6, 0xc6, 0xbd, 0x2a,
+	0x93, 0xa9, 0x7b, 0xf1, 0x29, 0x30, 0xee, 0x95, 0x07, 0xc4, 0xb8, 0x57, 0xdf, 0x0c, 0xda, 0x20,
+	0x17, 0x30, 0x2c, 0xeb, 0x13, 0x39, 0x29, 0x14, 0x5a, 0xe0, 0x7b, 0x76, 0x77, 0x2f, 0x9e, 0x05,
+	0x29, 0xcb, 0x87, 0x09, 0x52, 0x13, 0x2f, 0x13, 0xa4, 0xae, 0x35, 0x69, 0x90, 0xb2, 0x4a, 0x98,
+	0x20, 0x35, 0x95, 0x31, 0x41, 0xea, 0x92, 0x42, 0x1b, 0xe4, 0x29, 0x0c, 0x8a, 0x22, 0x21, 0x0c,
+	0x1d, 0x15, 0x2d, 0x31, 0x74, 0x54, 0xe5, 0x84, 0x36, 0xc8, 0x63, 0x80, 0x5f, 0x51, 0x1a, 0x61,
+	0x20, 0x23, 0x7d, 0xec, 0x46, 0x34, 0x66, 0x4e, 0x19, 0xd0, 0x2e, 0xdf, 0x41, 0xaf, 0xb0, 0x68,
+	0xe4, 0xb3, 0x3c, 0xf4, 0xcd, 0xa2, 0xcc, 0x26, 0x75, 0x50, 0xfb, 0xfe, 0x08, 0x83, 0xd2, 0x2a,
+	0x90, 0x3b, 0x66, 0x15, 0xcb, 0x8b, 0x36, 0x3b, 0xd9, 0x07, 0x67, 0xac, 0x95, 0xef, 0xb4, 0x61,
+	0xad, 0xb6, 0x2f, 0x86, 0xb5, 0xfa, 0x02, 0xd0, 0xc6, 0xcf, 0x13, 0x20, 0xab, 0xf8, 0x6a, 0xbe,
+	0x8a, 0x39, 0xc6, 0x62, 0x1e, 0xe0, 0xb5, 0x3a, 0xfa, 0xa6, 0xa3, 0xff, 0xef, 0x7d, 0xfd, 0x21,
+	0x00, 0x00, 0xff, 0xff, 0x3d, 0xbe, 0xae, 0xc8, 0x03, 0x0a, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// DexClient is the client API for Dex service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type DexClient interface {
+	// CreateClient creates a client.
+	CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error)
+	// UpdateClient updates an existing client
+	UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error)
+	// DeleteClient deletes the provided client.
+	DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error)
+	// CreatePassword creates a password.
+	CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error)
+	// UpdatePassword modifies existing password.
+	UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error)
+	// DeletePassword deletes the password.
+	DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error)
+	// ListPassword lists all password entries.
+	ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error)
+	// GetVersion returns version information of the server.
+	GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error)
+	// ListRefresh lists all the refresh token entries for a particular user.
+	ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error)
+	// RevokeRefresh revokes the refresh token for the provided user-client pair.
+	//
+	// 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 {
+	cc *grpc.ClientConn
+}
+
+func NewDexClient(cc *grpc.ClientConn) DexClient {
+	return &dexClient{cc}
+}
+
+func (c *dexClient) CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) {
+	out := new(CreateClientResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/CreateClient", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) {
+	out := new(UpdateClientResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/UpdateClient", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) {
+	out := new(DeleteClientResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/DeleteClient", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) {
+	out := new(CreatePasswordResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/CreatePassword", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) {
+	out := new(UpdatePasswordResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/UpdatePassword", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) {
+	out := new(DeletePasswordResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/DeletePassword", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) {
+	out := new(ListPasswordResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/ListPasswords", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) {
+	out := new(VersionResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/GetVersion", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) {
+	out := new(ListRefreshResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/ListRefresh", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) {
+	out := new(RevokeRefreshResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/RevokeRefresh", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *dexClient) VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) {
+	out := new(VerifyPasswordResp)
+	err := c.cc.Invoke(ctx, "/api.Dex/VerifyPassword", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// DexServer is the server API for Dex service.
+type DexServer interface {
+	// CreateClient creates a client.
+	CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error)
+	// UpdateClient updates an existing client
+	UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error)
+	// DeleteClient deletes the provided client.
+	DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error)
+	// CreatePassword creates a password.
+	CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error)
+	// UpdatePassword modifies existing password.
+	UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error)
+	// DeletePassword deletes the password.
+	DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error)
+	// ListPassword lists all password entries.
+	ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error)
+	// GetVersion returns version information of the server.
+	GetVersion(context.Context, *VersionReq) (*VersionResp, error)
+	// ListRefresh lists all the refresh token entries for a particular user.
+	ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error)
+	// RevokeRefresh revokes the refresh token for the provided user-client pair.
+	//
+	// 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)
+}
+
+// UnimplementedDexServer can be embedded to have forward compatible implementations.
+type UnimplementedDexServer struct {
+}
+
+func (*UnimplementedDexServer) CreateClient(ctx context.Context, req *CreateClientReq) (*CreateClientResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method CreateClient not implemented")
+}
+func (*UnimplementedDexServer) UpdateClient(ctx context.Context, req *UpdateClientReq) (*UpdateClientResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method UpdateClient not implemented")
+}
+func (*UnimplementedDexServer) DeleteClient(ctx context.Context, req *DeleteClientReq) (*DeleteClientResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method DeleteClient not implemented")
+}
+func (*UnimplementedDexServer) CreatePassword(ctx context.Context, req *CreatePasswordReq) (*CreatePasswordResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method CreatePassword not implemented")
+}
+func (*UnimplementedDexServer) UpdatePassword(ctx context.Context, req *UpdatePasswordReq) (*UpdatePasswordResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method UpdatePassword not implemented")
+}
+func (*UnimplementedDexServer) DeletePassword(ctx context.Context, req *DeletePasswordReq) (*DeletePasswordResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method DeletePassword not implemented")
+}
+func (*UnimplementedDexServer) ListPasswords(ctx context.Context, req *ListPasswordReq) (*ListPasswordResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ListPasswords not implemented")
+}
+func (*UnimplementedDexServer) GetVersion(ctx context.Context, req *VersionReq) (*VersionResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
+}
+func (*UnimplementedDexServer) ListRefresh(ctx context.Context, req *ListRefreshReq) (*ListRefreshResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ListRefresh not implemented")
+}
+func (*UnimplementedDexServer) RevokeRefresh(ctx context.Context, req *RevokeRefreshReq) (*RevokeRefreshResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method RevokeRefresh not implemented")
+}
+func (*UnimplementedDexServer) VerifyPassword(ctx context.Context, req *VerifyPasswordReq) (*VerifyPasswordResp, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method VerifyPassword not implemented")
+}
+
+func RegisterDexServer(s *grpc.Server, srv DexServer) {
+	s.RegisterService(&_Dex_serviceDesc, srv)
+}
+
+func _Dex_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CreateClientReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).CreateClient(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/CreateClient",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).CreateClient(ctx, req.(*CreateClientReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UpdateClientReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).UpdateClient(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/UpdateClient",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).UpdateClient(ctx, req.(*UpdateClientReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(DeleteClientReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).DeleteClient(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/DeleteClient",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).DeleteClient(ctx, req.(*DeleteClientReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_CreatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(CreatePasswordReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).CreatePassword(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/CreatePassword",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).CreatePassword(ctx, req.(*CreatePasswordReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(UpdatePasswordReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).UpdatePassword(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/UpdatePassword",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).UpdatePassword(ctx, req.(*UpdatePasswordReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_DeletePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(DeletePasswordReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).DeletePassword(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/DeletePassword",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).DeletePassword(ctx, req.(*DeletePasswordReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_ListPasswords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ListPasswordReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).ListPasswords(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/ListPasswords",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).ListPasswords(ctx, req.(*ListPasswordReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(VersionReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).GetVersion(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/GetVersion",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).GetVersion(ctx, req.(*VersionReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_ListRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ListRefreshReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).ListRefresh(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/ListRefresh",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).ListRefresh(ctx, req.(*ListRefreshReq))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Dex_RevokeRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(RevokeRefreshReq)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(DexServer).RevokeRefresh(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/api.Dex/RevokeRefresh",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(DexServer).RevokeRefresh(ctx, req.(*RevokeRefreshReq))
+	}
+	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),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "CreateClient",
+			Handler:    _Dex_CreateClient_Handler,
+		},
+		{
+			MethodName: "UpdateClient",
+			Handler:    _Dex_UpdateClient_Handler,
+		},
+		{
+			MethodName: "DeleteClient",
+			Handler:    _Dex_DeleteClient_Handler,
+		},
+		{
+			MethodName: "CreatePassword",
+			Handler:    _Dex_CreatePassword_Handler,
+		},
+		{
+			MethodName: "UpdatePassword",
+			Handler:    _Dex_UpdatePassword_Handler,
+		},
+		{
+			MethodName: "DeletePassword",
+			Handler:    _Dex_DeletePassword_Handler,
+		},
+		{
+			MethodName: "ListPasswords",
+			Handler:    _Dex_ListPasswords_Handler,
+		},
+		{
+			MethodName: "GetVersion",
+			Handler:    _Dex_GetVersion_Handler,
+		},
+		{
+			MethodName: "ListRefresh",
+			Handler:    _Dex_ListRefresh_Handler,
+		},
+		{
+			MethodName: "RevokeRefresh",
+			Handler:    _Dex_RevokeRefresh_Handler,
+		},
+		{
+			MethodName: "VerifyPassword",
+			Handler:    _Dex_VerifyPassword_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "api/v2/api.proto",
+}
diff --git a/api/v2/api.proto b/api/v2/api.proto
new file mode 100644
index 00000000..5d9ce1a1
--- /dev/null
+++ b/api/v2/api.proto
@@ -0,0 +1,187 @@
+syntax = "proto3";
+option java_package = "com.coreos.dex.api";
+
+package api;
+
+// Client represents an OAuth2 client.
+message Client {
+  string id = 1;
+  string secret = 2;
+  repeated string redirect_uris = 3;
+  repeated string trusted_peers = 4;
+  bool public = 5;
+  string name = 6;
+  string logo_url = 7;
+}
+
+// CreateClientReq is a request to make a client.
+message CreateClientReq {
+  Client client = 1;
+}
+
+// CreateClientResp returns the response from creating a client.
+message CreateClientResp {
+  bool already_exists = 1;
+  Client client = 2; 
+}
+
+// DeleteClientReq is a request to delete a client.
+message DeleteClientReq {
+  // The ID of the client.
+  string id = 1;
+}
+
+// DeleteClientResp determines if the client is deleted successfully. 
+message DeleteClientResp {
+  bool not_found = 1;
+}
+
+// UpdateClientReq is a request to update an exisitng client.
+message UpdateClientReq {
+    string id = 1;
+    repeated string redirect_uris = 2;
+    repeated string trusted_peers = 3;
+    string name = 4;
+    string logo_url = 5;
+}
+
+// UpdateClientResp returns the reponse form updating a client.
+message UpdateClientResp {
+    bool not_found = 1;
+}
+
+// TODO(ericchiang): expand this.
+
+// Password is an email for password mapping managed by the storage.
+message Password {
+  string email = 1;
+
+  // Currently we do not accept plain text passwords. Could be an option in the future.
+  bytes hash = 2;
+  string username = 3;
+  string user_id = 4;
+}
+
+// CreatePasswordReq is a request to make a password.
+message CreatePasswordReq {
+  Password password = 1;
+}
+
+// CreatePasswordResp returns the response from creating a password.
+message CreatePasswordResp {
+  bool already_exists = 1;
+}
+
+// UpdatePasswordReq is a request to modify an existing password.
+message UpdatePasswordReq {
+  // The email used to lookup the password. This field cannot be modified
+  string email = 1;
+  bytes new_hash = 2;
+  string new_username = 3;
+}
+
+// UpdatePasswordResp returns the response from modifying an existing password. 
+message UpdatePasswordResp {
+  bool not_found = 1;
+}
+
+// DeletePasswordReq is a request to delete a password.
+message DeletePasswordReq {
+  string email = 1;
+}
+
+// DeletePasswordResp returns the response from deleting a password. 
+message DeletePasswordResp {
+  bool not_found = 1;
+}
+
+// ListPasswordReq is a request to enumerate passwords.
+message ListPasswordReq {}
+
+// ListPasswordResp returns a list of passwords.
+message ListPasswordResp {
+  repeated Password passwords = 1;
+}
+
+// VersionReq is a request to fetch version info.
+message VersionReq {}
+
+// VersionResp holds the version info of components.
+message VersionResp {
+  // Semantic version of the server.
+  string server = 1;
+  // Numeric version of the API. It increases everytime a new call is added to the API.
+  // Clients should use this info to determine if the server supports specific features.
+  int32 api = 2;
+}
+
+// RefreshTokenRef contains the metadata for a refresh token that is managed by the storage.
+message RefreshTokenRef {
+  // ID of the refresh token.
+  string id = 1;
+  string client_id = 2;
+  int64 created_at = 5;
+  int64 last_used = 6;
+}
+
+// ListRefreshReq is a request to enumerate the refresh tokens of a user.
+message ListRefreshReq {
+  // The "sub" claim returned in the ID Token.
+  string user_id = 1;
+}
+
+// ListRefreshResp returns a list of refresh tokens for a user.
+message ListRefreshResp {
+  repeated RefreshTokenRef refresh_tokens = 1;
+}
+
+// RevokeRefreshReq is a request to revoke the refresh token of the user-client pair.
+message RevokeRefreshReq {
+  // The "sub" claim returned in the ID Token.
+  string user_id = 1;
+  string client_id = 2;
+}
+
+// RevokeRefreshResp determines if the refresh token is revoked successfully. 
+message RevokeRefreshResp {
+  // Set to true is refresh token was not found and token could not be revoked.
+  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.
+  rpc CreateClient(CreateClientReq) returns (CreateClientResp) {};
+  // UpdateClient updates an existing client
+  rpc UpdateClient(UpdateClientReq) returns (UpdateClientResp) {};
+  // DeleteClient deletes the provided client.
+  rpc DeleteClient(DeleteClientReq) returns (DeleteClientResp) {};
+  // CreatePassword creates a password.
+  rpc CreatePassword(CreatePasswordReq) returns (CreatePasswordResp) {};
+  // UpdatePassword modifies existing password.
+  rpc UpdatePassword(UpdatePasswordReq) returns (UpdatePasswordResp) {};
+  // DeletePassword deletes the password.
+  rpc DeletePassword(DeletePasswordReq) returns (DeletePasswordResp) {};
+  // ListPassword lists all password entries.
+  rpc ListPasswords(ListPasswordReq) returns (ListPasswordResp) {};
+  // GetVersion returns version information of the server.
+  rpc GetVersion(VersionReq) returns (VersionResp) {};
+  // ListRefresh lists all the refresh token entries for a particular user.
+  rpc ListRefresh(ListRefreshReq) returns (ListRefreshResp) {};
+  // RevokeRefresh revokes the refresh token for the provided user-client pair.
+  //
+  // 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/api/go.mod b/api/v2/go.mod
similarity index 71%
rename from api/go.mod
rename to api/v2/go.mod
index 285e0930..c6a3f064 100644
--- a/api/go.mod
+++ b/api/v2/go.mod
@@ -1,4 +1,4 @@
-module github.com/dexidp/dex/api
+module github.com/dexidp/dex/api/v2
 
 go 1.14
 
diff --git a/api/go.sum b/api/v2/go.sum
similarity index 100%
rename from api/go.sum
rename to api/v2/go.sum
diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go
index 5c8732aa..d0e8f9ac 100644
--- a/cmd/dex/serve.go
+++ b/cmd/dex/serve.go
@@ -23,7 +23,7 @@ import (
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/reflection"
 
-	"github.com/dexidp/dex/api"
+	"github.com/dexidp/dex/api/v2"
 	"github.com/dexidp/dex/pkg/log"
 	"github.com/dexidp/dex/server"
 	"github.com/dexidp/dex/storage"
diff --git a/examples/grpc-client/client.go b/examples/grpc-client/client.go
index e4cd526d..7b3949a1 100644
--- a/examples/grpc-client/client.go
+++ b/examples/grpc-client/client.go
@@ -12,7 +12,7 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials"
 
-	"github.com/dexidp/dex/api"
+	"github.com/dexidp/dex/api/v2"
 )
 
 func newDexClient(hostAndPort, caPath, clientCrt, clientKey string) (api.DexClient, error) {
diff --git a/go.mod b/go.mod
index 6afe828b..d7e4894e 100644
--- a/go.mod
+++ b/go.mod
@@ -9,7 +9,7 @@ require (
 	github.com/coreos/go-semver v0.3.0 // indirect
 	github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect
 	github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
-	github.com/dexidp/dex/api v0.0.0-00010101000000-000000000000
+	github.com/dexidp/dex/api/v2 v2.0.0-00010101000000-000000000000
 	github.com/felixge/httpsnoop v1.0.1
 	github.com/ghodss/yaml v1.0.0
 	github.com/go-sql-driver/mysql v1.5.0
@@ -35,11 +35,9 @@ require (
 	go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738
 	go.uber.org/atomic v1.4.0 // indirect
 	golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
-	golang.org/x/lint v0.0.0-20190409202823-959b441ac422
 	golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7
 	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
 	golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
-	golang.org/x/tools v0.0.0-20190813214729-9dba7caff850 // indirect
 	google.golang.org/api v0.15.0
 	google.golang.org/appengine v1.6.1 // indirect
 	google.golang.org/grpc v1.26.0
@@ -49,4 +47,4 @@ require (
 	sigs.k8s.io/testing_frameworks v0.1.2
 )
 
-replace github.com/dexidp/dex/api => ./api
+replace github.com/dexidp/dex/api/v2 => ./api/v2
diff --git a/go.sum b/go.sum
index 4f1e5763..54abd1b1 100644
--- a/go.sum
+++ b/go.sum
@@ -336,7 +336,6 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
 golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -392,9 +391,6 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
 golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190813214729-9dba7caff850 h1:I+2i9HEjvv+3iJrNMhjMJOMF/FXz9eI08iBlDF4w24I=
-golang.org/x/tools v0.0.0-20190813214729-9dba7caff850/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
diff --git a/server/api.go b/server/api.go
index 55784b90..c815ada4 100644
--- a/server/api.go
+++ b/server/api.go
@@ -7,7 +7,7 @@ import (
 
 	"golang.org/x/crypto/bcrypt"
 
-	"github.com/dexidp/dex/api"
+	"github.com/dexidp/dex/api/v2"
 	"github.com/dexidp/dex/pkg/log"
 	"github.com/dexidp/dex/server/internal"
 	"github.com/dexidp/dex/storage"


Test patch:
diff --git a/server/api_test.go b/server/api_test.go
index 511d5d39..61ec9942 100644
--- a/server/api_test.go
+++ b/server/api_test.go
@@ -10,7 +10,7 @@ import (
 	"github.com/sirupsen/logrus"
 	"google.golang.org/grpc"
 
-	"github.com/dexidp/dex/api"
+	"github.com/dexidp/dex/api/v2"
 	"github.com/dexidp/dex/pkg/log"
 	"github.com/dexidp/dex/server/internal"
 	"github.com/dexidp/dex/storage"