diff --git a/cmd/dex/config.go b/cmd/dex/config.go
index 7279d29acce9ddc97c5ee622f43be2dbe633786f..2c0cae29ccec3aa92d9e9ff69fb40d382a618020 100644
--- a/cmd/dex/config.go
+++ b/cmd/dex/config.go
@@ -9,13 +9,6 @@ import (
 	"github.com/Sirupsen/logrus"
 	"golang.org/x/crypto/bcrypt"
 
-	"github.com/coreos/dex/connector"
-	"github.com/coreos/dex/connector/github"
-	"github.com/coreos/dex/connector/gitlab"
-	"github.com/coreos/dex/connector/ldap"
-	"github.com/coreos/dex/connector/mock"
-	"github.com/coreos/dex/connector/oidc"
-	"github.com/coreos/dex/connector/saml"
 	"github.com/coreos/dex/server"
 	"github.com/coreos/dex/storage"
 	"github.com/coreos/dex/storage/kubernetes"
@@ -25,17 +18,20 @@ import (
 
 // Config is the config format for the main application.
 type Config struct {
-	Issuer     string      `json:"issuer"`
-	Storage    Storage     `json:"storage"`
-	Connectors []Connector `json:"connectors"`
-	Web        Web         `json:"web"`
-	OAuth2     OAuth2      `json:"oauth2"`
-	GRPC       GRPC        `json:"grpc"`
-	Expiry     Expiry      `json:"expiry"`
-	Logger     Logger      `json:"logger"`
+	Issuer  string  `json:"issuer"`
+	Storage Storage `json:"storage"`
+	Web     Web     `json:"web"`
+	OAuth2  OAuth2  `json:"oauth2"`
+	GRPC    GRPC    `json:"grpc"`
+	Expiry  Expiry  `json:"expiry"`
+	Logger  Logger  `json:"logger"`
 
 	Frontend server.WebConfig `json:"frontend"`
 
+	// StaticConnectors are user defined connectors specified in the ConfigMap
+	// Write operations, like updating a connector, will fail.
+	StaticConnectors []Connector `json:"connectors"`
+
 	// StaticClients cause the server to use this list of clients rather than
 	// querying the storage. Write operations, like creating a client, will fail.
 	StaticClients []storage.Client `json:"staticClients"`
@@ -170,24 +166,7 @@ type Connector struct {
 	Name string `json:"name"`
 	ID   string `json:"id"`
 
-	Config ConnectorConfig `json:"config"`
-}
-
-// ConnectorConfig is a configuration that can open a connector.
-type ConnectorConfig interface {
-	Open(logrus.FieldLogger) (connector.Connector, error)
-}
-
-var connectors = map[string]func() ConnectorConfig{
-	"mockCallback": func() ConnectorConfig { return new(mock.CallbackConfig) },
-	"mockPassword": func() ConnectorConfig { return new(mock.PasswordConfig) },
-	"ldap":         func() ConnectorConfig { return new(ldap.Config) },
-	"github":       func() ConnectorConfig { return new(github.Config) },
-	"gitlab":       func() ConnectorConfig { return new(gitlab.Config) },
-	"oidc":         func() ConnectorConfig { return new(oidc.Config) },
-	"saml":         func() ConnectorConfig { return new(saml.Config) },
-	// Keep around for backwards compatibility.
-	"samlExperimental": func() ConnectorConfig { return new(saml.Config) },
+	Config server.ConnectorConfig `json:"config"`
 }
 
 // UnmarshalJSON allows Connector to implement the unmarshaler interface to
@@ -203,7 +182,7 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
 	if err := json.Unmarshal(b, &conn); err != nil {
 		return fmt.Errorf("parse connector: %v", err)
 	}
-	f, ok := connectors[conn.Type]
+	f, ok := server.ConnectorsConfig[conn.Type]
 	if !ok {
 		return fmt.Errorf("unknown connector type %q", conn.Type)
 	}
@@ -224,6 +203,21 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
 	return nil
 }
 
+// ToStorageConnector converts an object to storage connector type.
+func ToStorageConnector(c Connector) (storage.Connector, error) {
+	data, err := json.Marshal(c.Config)
+	if err != nil {
+		return storage.Connector{}, fmt.Errorf("failed to marshal connector config: %v", err)
+	}
+
+	return storage.Connector{
+		ID:     c.ID,
+		Type:   c.Type,
+		Name:   c.Name,
+		Config: data,
+	}, nil
+}
+
 // Expiry holds configuration for the validity period of components.
 type Expiry struct {
 	// SigningKeys defines the duration of time after which the SigningKeys will be rotated.
diff --git a/cmd/dex/config_test.go b/cmd/dex/config_test.go
index 004fec00c7d3429a8f7de1c9db317e82e5faf13d..f5913cdec4fc0d0d228c8eded84eb4e12393e862 100644
--- a/cmd/dex/config_test.go
+++ b/cmd/dex/config_test.go
@@ -86,7 +86,7 @@ logger:
 				},
 			},
 		},
-		Connectors: []Connector{
+		StaticConnectors: []Connector{
 			{
 				Type:   "mockCallback",
 				ID:     "mock",
diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go
index 08d9fdb0744b7d20ae746ce5fe2f92b1c775e72c..8ee58b773e2eb5184c763c930b80fe523c36b60b 100644
--- a/cmd/dex/serve.go
+++ b/cmd/dex/serve.go
@@ -74,7 +74,6 @@ func serve(cmd *cobra.Command, args []string) error {
 		errMsg string
 	}{
 		{c.Issuer == "", "no issuer specified in config file"},
-		{len(c.Connectors) == 0 && !c.EnablePasswordDB, "no connectors supplied in config file"},
 		{!c.EnablePasswordDB && len(c.StaticPasswords) != 0, "cannot specify static passwords without enabling password db"},
 		{c.Storage.Config == nil, "no storage suppied in config file"},
 		{c.Web.HTTP == "" && c.Web.HTTPS == "", "must supply a HTTP/HTTPS  address to listen on"},
@@ -128,34 +127,6 @@ func serve(cmd *cobra.Command, args []string) error {
 		}
 	}
 
-	connectors := make([]server.Connector, len(c.Connectors))
-	for i, conn := range c.Connectors {
-		if conn.ID == "" {
-			return fmt.Errorf("invalid config: no ID field for connector %d", i)
-		}
-		if conn.Config == nil {
-			return fmt.Errorf("invalid config: no config field for connector %q", conn.ID)
-		}
-		if conn.Name == "" {
-			return fmt.Errorf("invalid config: no Name field for connector %q", conn.ID)
-		}
-		logger.Infof("config connector: %s", conn.ID)
-
-		connectorLogger := logger.WithField("connector", conn.Name)
-		c, err := conn.Config.Open(connectorLogger)
-		if err != nil {
-			return fmt.Errorf("failed to create connector %s: %v", conn.ID, err)
-		}
-		connectors[i] = server.Connector{
-			ID:          conn.ID,
-			DisplayName: conn.Name,
-			Connector:   c,
-		}
-	}
-	if c.EnablePasswordDB {
-		logger.Infof("config connector: local passwords enabled")
-	}
-
 	s, err := c.Storage.Config.Open(logger)
 	if err != nil {
 		return fmt.Errorf("failed to initialize storage: %v", err)
@@ -176,6 +147,35 @@ func serve(cmd *cobra.Command, args []string) error {
 		s = storage.WithStaticPasswords(s, passwords)
 	}
 
+	if c.EnablePasswordDB {
+		c.StaticConnectors = append(c.StaticConnectors, Connector{
+			ID:   server.LocalConnector,
+			Name: "Email",
+			Type: server.LocalConnector,
+		})
+		logger.Infof("config connector: local passwords enabled")
+	}
+
+	storageConnectors := make([]storage.Connector, len(c.StaticConnectors))
+	for i, c := range c.StaticConnectors {
+		if c.ID == "" || c.Name == "" || c.Type == "" {
+			return fmt.Errorf("invalid config: ID, Type and Name fields are required for a connector")
+		}
+		if c.Config == nil {
+			return fmt.Errorf("invalid config: no config field for connector %q", c.ID)
+		}
+		logger.Infof("config connector: %s", c.ID)
+
+		// convert to a storage connector object
+		conn, err := ToStorageConnector(c)
+		if err != nil {
+			return fmt.Errorf("failed to initialize storage connectors: %v", err)
+		}
+		storageConnectors[i] = conn
+
+	}
+	s = storage.WithStaticConnectors(s, storageConnectors)
+
 	if len(c.OAuth2.ResponseTypes) > 0 {
 		logger.Infof("config response types accepted: %s", c.OAuth2.ResponseTypes)
 	}
@@ -194,10 +194,8 @@ func serve(cmd *cobra.Command, args []string) error {
 		SkipApprovalScreen:     c.OAuth2.SkipApprovalScreen,
 		AllowedOrigins:         c.Web.AllowedOrigins,
 		Issuer:                 c.Issuer,
-		Connectors:             connectors,
 		Storage:                s,
 		Web:                    c.Frontend,
-		EnablePasswordDB:       c.EnablePasswordDB,
 		Logger:                 logger,
 		Now:                    now,
 	}
diff --git a/server/handlers.go b/server/handlers.go
index 78eae26c57a95e0930fbb292e957c27a4d0b65a0..683c6c208b6000a5cc5ba51b77720243bb495e85 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -167,24 +167,31 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	if len(s.connectors) == 1 {
-		for id := range s.connectors {
+	connectors, e := s.storage.ListConnectors()
+	if e != nil {
+		s.logger.Errorf("Failed to get list of connectors: %v", err)
+		s.renderError(w, http.StatusInternalServerError, "Failed to retrieve connector list.")
+		return
+	}
+
+	if len(connectors) == 1 {
+		for _, c := range connectors {
 			// TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter
 			// on create the auth request.
-			http.Redirect(w, r, s.absPath("/auth", id)+"?req="+authReq.ID, http.StatusFound)
+			http.Redirect(w, r, s.absPath("/auth", c.ID)+"?req="+authReq.ID, http.StatusFound)
 			return
 		}
 	}
 
-	connectorInfos := make([]connectorInfo, len(s.connectors))
+	connectorInfos := make([]connectorInfo, len(connectors))
 	i := 0
-	for id, conn := range s.connectors {
+	for _, conn := range connectors {
 		connectorInfos[i] = connectorInfo{
-			ID:   id,
-			Name: conn.DisplayName,
+			ID:   conn.ID,
+			Name: conn.Name,
 			// TODO(ericchiang): Make this pass on r.URL.RawQuery and let something latter
 			// on create the auth request.
-			URL: s.absPath("/auth", id) + "?req=" + authReq.ID,
+			URL: s.absPath("/auth", conn.ID) + "?req=" + authReq.ID,
 		}
 		i++
 	}
@@ -196,10 +203,10 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
 
 func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
 	connID := mux.Vars(r)["connector"]
-	conn, ok := s.connectors[connID]
-	if !ok {
-		s.logger.Errorf("Failed to create authorization request.")
-		s.renderError(w, http.StatusBadRequest, "Requested resource does not exist.")
+	conn, err := s.getConnector(connID)
+	if err != nil {
+		s.logger.Errorf("Failed to create authorization request: %v", err)
+		s.renderError(w, http.StatusBadRequest, "Requested resource does not exist")
 		return
 	}
 
@@ -339,8 +346,9 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
 		return
 	}
 
-	conn, ok := s.connectors[authReq.ConnectorID]
-	if !ok {
+	conn, err := s.getConnector(authReq.ConnectorID)
+	if err != nil {
+		s.logger.Errorf("Failed to get connector with id %q : %v", authReq.ConnectorID, err)
 		s.renderError(w, http.StatusInternalServerError, "Requested resource does not exist.")
 		return
 	}
@@ -649,13 +657,14 @@ func (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client s
 		// Ensure the connector supports refresh tokens.
 		//
 		// Connectors like `saml` do not implement RefreshConnector.
-		conn, ok := s.connectors[authCode.ConnectorID]
-		if !ok {
-			s.logger.Errorf("connector ID not found: %q", authCode.ConnectorID)
+		conn, err := s.getConnector(authCode.ConnectorID)
+		if err != nil {
+			s.logger.Errorf("connector with ID %q not found: %v", authCode.ConnectorID, err)
 			s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
 			return false
 		}
-		_, ok = conn.Connector.(connector.RefreshConnector)
+
+		_, ok := conn.Connector.(connector.RefreshConnector)
 		if !ok {
 			return false
 		}
@@ -841,9 +850,9 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
 		scopes = requestedScopes
 	}
 
-	conn, ok := s.connectors[refresh.ConnectorID]
-	if !ok {
-		s.logger.Errorf("connector ID not found: %q", refresh.ConnectorID)
+	conn, err := s.getConnector(refresh.ConnectorID)
+	if err != nil {
+		s.logger.Errorf("connector with ID %q not found: %v", refresh.ConnectorID, err)
 		s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
 		return
 	}
diff --git a/server/server.go b/server/server.go
index 226558f738ad3e8e909ae87206b9841466fcdf72..829de4c2dd647716b63165a9dd8a6d36e746513b 100644
--- a/server/server.go
+++ b/server/server.go
@@ -2,11 +2,13 @@ package server
 
 import (
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
 	"net/url"
 	"path"
+	"sync"
 	"sync/atomic"
 	"time"
 
@@ -17,14 +19,23 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/coreos/dex/connector"
+	"github.com/coreos/dex/connector/github"
+	"github.com/coreos/dex/connector/gitlab"
+	"github.com/coreos/dex/connector/ldap"
+	"github.com/coreos/dex/connector/mock"
+	"github.com/coreos/dex/connector/oidc"
+	"github.com/coreos/dex/connector/saml"
 	"github.com/coreos/dex/storage"
 )
 
-// Connector is a connector with metadata.
+// LocalConnector is the local passwordDB connector which is an internal
+// connector maintained by the server.
+const LocalConnector = "local"
+
+// Connector is a connector with resource version metadata.
 type Connector struct {
-	ID          string
-	DisplayName string
-	Connector   connector.Connector
+	ResourceVersion string
+	Connector       connector.Connector
 }
 
 // Config holds the server's configuration options.
@@ -36,9 +47,6 @@ type Config struct {
 	// The backing persistence layer.
 	Storage storage.Storage
 
-	// Strategies for federated identity.
-	Connectors []Connector
-
 	// Valid values are "code" to enable the code flow and "token" to enable the implicit
 	// flow. If no response types are supplied this value defaults to "code".
 	SupportedResponseTypes []string
@@ -60,8 +68,6 @@ type Config struct {
 	// If specified, the server will use this function for determining time.
 	Now func() time.Time
 
-	EnablePasswordDB bool
-
 	Web WebConfig
 
 	Logger logrus.FieldLogger
@@ -103,7 +109,9 @@ func value(val, defaultValue time.Duration) time.Duration {
 type Server struct {
 	issuerURL url.URL
 
-	// Read-only map of connector IDs to connectors.
+	// mutex for the connectors map.
+	mu sync.Mutex
+	// Map of connector IDs to connectors.
 	connectors map[string]Connector
 
 	storage storage.Storage
@@ -137,17 +145,7 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
 	if err != nil {
 		return nil, fmt.Errorf("server: can't parse issuer URL")
 	}
-	if c.EnablePasswordDB {
-		c.Connectors = append(c.Connectors, Connector{
-			ID:          "local",
-			DisplayName: "Email",
-			Connector:   newPasswordDB(c.Storage),
-		})
-	}
 
-	if len(c.Connectors) == 0 {
-		return nil, errors.New("server: no connectors specified")
-	}
 	if c.Storage == nil {
 		return nil, errors.New("server: storage cannot be nil")
 	}
@@ -195,8 +193,21 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
 		logger:                 c.Logger,
 	}
 
-	for _, conn := range c.Connectors {
-		s.connectors[conn.ID] = conn
+	// Retrieves connector objects in backend storage. This list includes the static connectors
+	// defined in the ConfigMap and dynamic connectors retrieved from the storage.
+	storageConnectors, err := c.Storage.ListConnectors()
+	if err != nil {
+		return nil, fmt.Errorf("server: failed to list connector objects from storage: %v", err)
+	}
+
+	if len(storageConnectors) == 0 && len(s.connectors) == 0 {
+		return nil, errors.New("server: no connectors specified")
+	}
+
+	for _, conn := range storageConnectors {
+		if _, err := s.OpenConnector(conn); err != nil {
+			return nil, fmt.Errorf("server: Failed to open connector %s: %v", conn.ID, err)
+		}
 	}
 
 	r := mux.NewRouter()
@@ -362,3 +373,99 @@ func (s *Server) startGarbageCollection(ctx context.Context, frequency time.Dura
 	}()
 	return
 }
+
+// ConnectorConfig is a configuration that can open a connector.
+type ConnectorConfig interface {
+	Open(logrus.FieldLogger) (connector.Connector, error)
+}
+
+// ConnectorsConfig variable provides an easy way to return a config struct
+// depending on the connector type.
+var ConnectorsConfig = map[string]func() ConnectorConfig{
+	"mockCallback": func() ConnectorConfig { return new(mock.CallbackConfig) },
+	"mockPassword": func() ConnectorConfig { return new(mock.PasswordConfig) },
+	"ldap":         func() ConnectorConfig { return new(ldap.Config) },
+	"github":       func() ConnectorConfig { return new(github.Config) },
+	"gitlab":       func() ConnectorConfig { return new(gitlab.Config) },
+	"oidc":         func() ConnectorConfig { return new(oidc.Config) },
+	"saml":         func() ConnectorConfig { return new(saml.Config) },
+	// Keep around for backwards compatibility.
+	"samlExperimental": func() ConnectorConfig { return new(saml.Config) },
+}
+
+// openConnector will parse the connector config and open the connector.
+func openConnector(logger logrus.FieldLogger, conn storage.Connector) (connector.Connector, error) {
+	var c connector.Connector
+
+	f, ok := ConnectorsConfig[conn.Type]
+	if !ok {
+		return c, fmt.Errorf("unknown connector type %q", conn.Type)
+	}
+
+	connConfig := f()
+	if len(conn.Config) != 0 {
+		data := []byte(string(conn.Config))
+		if err := json.Unmarshal(data, connConfig); err != nil {
+			return c, fmt.Errorf("parse connector config: %v", err)
+		}
+	}
+
+	c, err := connConfig.Open(logger)
+	if err != nil {
+		return c, fmt.Errorf("failed to create connector %s: %v", conn.ID, err)
+	}
+
+	return c, nil
+}
+
+// OpenConnector updates server connector map with specified connector object.
+func (s *Server) OpenConnector(conn storage.Connector) (Connector, error) {
+	var c connector.Connector
+
+	if conn.Type == LocalConnector {
+		c = newPasswordDB(s.storage)
+	} else {
+		var err error
+		c, err = openConnector(s.logger.WithField("connector", conn.Name), conn)
+		if err != nil {
+			return Connector{}, fmt.Errorf("failed to open connector: %v", err)
+		}
+	}
+
+	connector := Connector{
+		ResourceVersion: conn.ResourceVersion,
+		Connector:       c,
+	}
+	s.mu.Lock()
+	s.connectors[conn.ID] = connector
+	s.mu.Unlock()
+
+	return connector, nil
+}
+
+// getConnector retrieves the connector object with the given id from the storage
+// and updates the connector list for server if necessary.
+func (s *Server) getConnector(id string) (Connector, error) {
+	storageConnector, err := s.storage.GetConnector(id)
+	if err != nil {
+		return Connector{}, fmt.Errorf("failed to get connector object from storage: %v", err)
+	}
+
+	var conn Connector
+	var ok bool
+	s.mu.Lock()
+	conn, ok = s.connectors[id]
+	s.mu.Unlock()
+
+	if !ok || storageConnector.ResourceVersion != conn.ResourceVersion {
+		// Connector object does not exist in server connectors map or
+		// has been updated in the storage. Need to get latest.
+		conn, err := s.OpenConnector(storageConnector)
+		if err != nil {
+			return Connector{}, fmt.Errorf("failed to open connector: %v", err)
+		}
+		return conn, nil
+	}
+
+	return conn, nil
+}
diff --git a/server/server_test.go b/server/server_test.go
index e326e93c0f8e1c2fcf6d317d578a04687a4c6443..844c7f7b47c598b8b6e72672d2a22e8e240151d2 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -89,13 +89,6 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
 	config := Config{
 		Issuer:  s.URL,
 		Storage: memory.New(logger),
-		Connectors: []Connector{
-			{
-				ID:          "mock",
-				DisplayName: "Mock",
-				Connector:   mock.NewCallbackConnector(logger),
-			},
-		},
 		Web: WebConfig{
 			Dir: filepath.Join(os.Getenv("GOPATH"), "src/github.com/coreos/dex/web"),
 		},
@@ -106,6 +99,16 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
 	}
 	s.URL = config.Issuer
 
+	connector := storage.Connector{
+		ID:              "mock",
+		Type:            "mockCallback",
+		Name:            "Mock",
+		ResourceVersion: "1",
+	}
+	if err := config.Storage.CreateConnector(connector); err != nil {
+		t.Fatalf("create connector: %v", err)
+	}
+
 	var err error
 	if server, err = newServer(ctx, config, staticRotationStrategy(testKey)); err != nil {
 		t.Fatal(err)
@@ -416,29 +419,16 @@ func TestOAuth2CodeFlow(t *testing.T) {
 			defer cancel()
 
 			// Setup a dex server.
-			logger := &logrus.Logger{
-				Out:       os.Stderr,
-				Formatter: &logrus.TextFormatter{DisableColors: true},
-				Level:     logrus.DebugLevel,
-			}
 			httpServer, s := newTestServer(ctx, t, func(c *Config) {
 				c.Issuer = c.Issuer + "/non-root-path"
 				c.Now = now
 				c.IDTokensValidFor = idTokensValidFor
-
-				// Testing connector that redirects without interaction with
-				// the user.
-				conn = mock.NewCallbackConnector(logger).(*mock.Callback)
-				c.Connectors = []Connector{
-					{
-						ID:          "mock",
-						DisplayName: "mock",
-						Connector:   conn,
-					},
-				}
 			})
 			defer httpServer.Close()
 
+			mockConn := s.connectors["mock"]
+			conn = mockConn.Connector.(*mock.Callback)
+
 			// Query server's provider metadata.
 			p, err := oidc.NewProvider(ctx, httpServer.URL)
 			if err != nil {
diff --git a/storage/conformance/conformance.go b/storage/conformance/conformance.go
index 50e705ba471f8a4b709c11658e63df29f545485d..0bfc474566b6309b8e5bcf9ee6870ac4506d6aa9 100644
--- a/storage/conformance/conformance.go
+++ b/storage/conformance/conformance.go
@@ -628,6 +628,10 @@ func testConnectorCRUD(t *testing.T, s storage.Storage) {
 	c1.Type = "oidc"
 	getAndCompare(id1, c1)
 
+	if _, err := s.ListConnectors(); err != nil {
+		t.Fatalf("failed to list connectors: %v", err)
+	}
+
 	if err := s.DeleteConnector(c1.ID); err != nil {
 		t.Fatalf("failed to delete connector: %v", err)
 	}
diff --git a/storage/memory/static_test.go b/storage/memory/static_test.go
index 331406124109f50a6445e515f136acace4f61083..b29b5ea908d8ba2344f48f6bc5b3fdb59f984564 100644
--- a/storage/memory/static_test.go
+++ b/storage/memory/static_test.go
@@ -190,3 +190,94 @@ func TestStaticPasswords(t *testing.T) {
 		}
 	}
 }
+
+func TestStaticConnectors(t *testing.T) {
+	logger := &logrus.Logger{
+		Out:       os.Stderr,
+		Formatter: &logrus.TextFormatter{DisableColors: true},
+		Level:     logrus.DebugLevel,
+	}
+	backing := New(logger)
+
+	config1 := []byte(`{"issuer": "https://accounts.google.com"}`)
+	config2 := []byte(`{"host": "ldap.example.com:636"}`)
+	config3 := []byte(`{"issuer": "https://example.com"}`)
+
+	c1 := storage.Connector{ID: storage.NewID(), Type: "oidc", Name: "oidc", ResourceVersion: "1", Config: config1}
+	c2 := storage.Connector{ID: storage.NewID(), Type: "ldap", Name: "ldap", ResourceVersion: "1", Config: config2}
+	c3 := storage.Connector{ID: storage.NewID(), Type: "saml", Name: "saml", ResourceVersion: "1", Config: config3}
+
+	backing.CreateConnector(c1)
+	s := storage.WithStaticConnectors(backing, []storage.Connector{c2})
+
+	tests := []struct {
+		name    string
+		action  func() error
+		wantErr bool
+	}{
+		{
+			name: "get connector from static storage",
+			action: func() error {
+				_, err := s.GetConnector(c2.ID)
+				return err
+			},
+		},
+		{
+			name: "get connector from backing storage",
+			action: func() error {
+				_, err := s.GetConnector(c1.ID)
+				return err
+			},
+		},
+		{
+			name: "update static connector",
+			action: func() error {
+				updater := func(c storage.Connector) (storage.Connector, error) {
+					c.Name = "New"
+					return c, nil
+				}
+				return s.UpdateConnector(c2.ID, updater)
+			},
+			wantErr: true,
+		},
+		{
+			name: "update non-static connector",
+			action: func() error {
+				updater := func(c storage.Connector) (storage.Connector, error) {
+					c.Name = "New"
+					return c, nil
+				}
+				return s.UpdateConnector(c1.ID, updater)
+			},
+		},
+		{
+			name: "list connectors",
+			action: func() error {
+				connectors, err := s.ListConnectors()
+				if err != nil {
+					return err
+				}
+				if n := len(connectors); n != 2 {
+					return fmt.Errorf("expected 2 connectors got %d", n)
+				}
+				return nil
+			},
+		},
+		{
+			name: "create connector",
+			action: func() error {
+				return s.CreateConnector(c3)
+			},
+		},
+	}
+
+	for _, tc := range tests {
+		err := tc.action()
+		if err != nil && !tc.wantErr {
+			t.Errorf("%s: %v", tc.name, err)
+		}
+		if err == nil && tc.wantErr {
+			t.Errorf("%s: expected error, didn't get one", tc.name)
+		}
+	}
+}
diff --git a/storage/static.go b/storage/static.go
index d5b4f83f7d4c0d906cf363134cf03a085adc351d..53bd9bfe6682f54ae3b89754644ea7a62b5f82ba 100644
--- a/storage/static.go
+++ b/storage/static.go
@@ -150,3 +150,73 @@ func (s staticPasswordsStorage) UpdatePassword(email string, updater func(old Pa
 	}
 	return s.Storage.UpdatePassword(email, updater)
 }
+
+// staticConnectorsStorage represents a storage with read-only set of connectors.
+type staticConnectorsStorage struct {
+	Storage
+
+	// A read-only set of connectors.
+	connectors     []Connector
+	connectorsByID map[string]Connector
+}
+
+// WithStaticConnectors returns a storage with a read-only set of Connectors. Write actions,
+// such as updating existing Connectors, will fail.
+func WithStaticConnectors(s Storage, staticConnectors []Connector) Storage {
+	connectorsByID := make(map[string]Connector, len(staticConnectors))
+	for _, c := range staticConnectors {
+		connectorsByID[c.ID] = c
+	}
+	return staticConnectorsStorage{s, staticConnectors, connectorsByID}
+}
+
+func (s staticConnectorsStorage) isStatic(id string) bool {
+	_, ok := s.connectorsByID[id]
+	return ok
+}
+
+func (s staticConnectorsStorage) GetConnector(id string) (Connector, error) {
+	if connector, ok := s.connectorsByID[id]; ok {
+		return connector, nil
+	}
+	return s.Storage.GetConnector(id)
+}
+
+func (s staticConnectorsStorage) ListConnectors() ([]Connector, error) {
+	connectors, err := s.Storage.ListConnectors()
+	if err != nil {
+		return nil, err
+	}
+
+	n := 0
+	for _, connector := range connectors {
+		// If an entry has the same id as those provided in the static
+		// values, prefer the static value.
+		if !s.isStatic(connector.ID) {
+			connectors[n] = connector
+			n++
+		}
+	}
+	return append(connectors[:n], s.connectors...), nil
+}
+
+func (s staticConnectorsStorage) CreateConnector(c Connector) error {
+	if s.isStatic(c.ID) {
+		return errors.New("static connectors: read-only cannot create connector")
+	}
+	return s.Storage.CreateConnector(c)
+}
+
+func (s staticConnectorsStorage) DeleteConnector(id string) error {
+	if s.isStatic(id) {
+		return errors.New("static connectors: read-only cannot delete connector")
+	}
+	return s.Storage.DeleteConnector(id)
+}
+
+func (s staticConnectorsStorage) UpdateConnector(id string, updater func(old Connector) (Connector, error)) error {
+	if s.isStatic(id) {
+		return errors.New("static connectors: read-only cannot update connector")
+	}
+	return s.Storage.UpdateConnector(id, updater)
+}
diff --git a/storage/storage.go b/storage/storage.go
index 92ac2ee25f1060cc8adb13e71b1e59a9a117ff88..8ea5ab2c829f07d417f8ebef4a9ae955eef8519e 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -298,17 +298,17 @@ type Password struct {
 // Connector is an object that contains the metadata about connectors used to login to Dex.
 type Connector struct {
 	// ID that will uniquely identify the connector object.
-	ID string
+	ID string `json:"id"`
 	// The Type of the connector. E.g. 'oidc' or 'ldap'
-	Type string
+	Type string `json:"type"`
 	// The Name of the connector that is used when displaying it to the end user.
-	Name string
+	Name string `json:"name"`
 	// ResourceVersion is the static versioning used to keep track of dynamic configuration
 	// changes to the connector object made by the API calls.
-	ResourceVersion string
+	ResourceVersion string `json:"resourceVersion"`
 	// Config holds all the configuration information specific to the connector type. Since there
 	// no generic struct we can use for this purpose, it is stored as a byte stream.
-	Config []byte
+	Config []byte `json:"email"`
 }
 
 // VerificationKey is a rotated signing key which can still be used to verify