diff --git a/cmd/poke/config.go b/cmd/poke/config.go
index 3d840a40532ef238a7b96dd0050d6a427d232f26..fc06b208eeb292e8ef5ca4955b45a2b2b8da6e55 100644
--- a/cmd/poke/config.go
+++ b/cmd/poke/config.go
@@ -9,6 +9,7 @@ import (
 	"github.com/coreos/poke/connector/mock"
 	"github.com/coreos/poke/storage"
 	"github.com/coreos/poke/storage/kubernetes"
+	"github.com/coreos/poke/storage/memory"
 )
 
 // Config is the config format for the main application.
@@ -17,6 +18,8 @@ type Config struct {
 	Storage    Storage     `yaml:"storage"`
 	Connectors []Connector `yaml:"connectors"`
 	Web        Web         `yaml:"web"`
+
+	StaticClients []storage.Client `yaml:"staticClients"`
 }
 
 // Web is the config format for the HTTP server.
@@ -46,9 +49,12 @@ func (s *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
 	var c struct {
 		Config StorageConfig `yaml:"config"`
 	}
+	// TODO(ericchiang): replace this with a registration process.
 	switch storageMeta.Type {
 	case "kubernetes":
 		c.Config = &kubernetes.Config{}
+	case "memory":
+		c.Config = &memory.Config{}
 	default:
 		return fmt.Errorf("unknown storage type %q", storageMeta.Type)
 	}
diff --git a/cmd/poke/config_test.go b/cmd/poke/config_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f4158bc313d6565f12c67b42c5aa4c2c479d9ce8
--- /dev/null
+++ b/cmd/poke/config_test.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+	"testing"
+
+	"github.com/coreos/poke/storage"
+	"github.com/kylelemons/godebug/pretty"
+
+	yaml "gopkg.in/yaml.v2"
+)
+
+func TestUnmarshalClients(t *testing.T) {
+	data := `staticClients:
+- id: example-app
+  redirectURIs:
+  - 'http://127.0.0.1:5555/callback'
+  name: 'Example App'
+  secret: ZXhhbXBsZS1hcHAtc2VjcmV0
+`
+	var c Config
+	if err := yaml.Unmarshal([]byte(data), &c); err != nil {
+		t.Fatal(err)
+	}
+
+	wantClients := []storage.Client{
+		{
+			ID:     "example-app",
+			Name:   "Example App",
+			Secret: "ZXhhbXBsZS1hcHAtc2VjcmV0",
+			RedirectURIs: []string{
+				"http://127.0.0.1:5555/callback",
+			},
+		},
+	}
+
+	if diff := pretty.Compare(wantClients, c.StaticClients); diff != "" {
+		t.Errorf("did not get expected clients: %s", diff)
+	}
+}
diff --git a/cmd/poke/serve.go b/cmd/poke/serve.go
index 28611f5048adc10c7c30828d200018680f0f516e..24bb491768cf9bb0bfb8c3e9da63832fb8d593ec 100644
--- a/cmd/poke/serve.go
+++ b/cmd/poke/serve.go
@@ -7,10 +7,11 @@ import (
 	"log"
 	"net/http"
 
+	"github.com/spf13/cobra"
 	yaml "gopkg.in/yaml.v2"
 
 	"github.com/coreos/poke/server"
-	"github.com/spf13/cobra"
+	"github.com/coreos/poke/storage"
 )
 
 func commandServe() *cobra.Command {
@@ -83,6 +84,9 @@ func serve(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return fmt.Errorf("initializing storage: %v", err)
 	}
+	if len(c.StaticClients) > 0 {
+		s = storage.WithStaticClients(s, c.StaticClients)
+	}
 
 	serverConfig := server.Config{
 		Issuer:     c.Issuer,
diff --git a/example/config-dev.yaml b/example/config-dev.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d3d01afb8a21452a43b2ca1639b4d9733520b3cc
--- /dev/null
+++ b/example/config-dev.yaml
@@ -0,0 +1,27 @@
+issuer: http://127.0.0.1:5556
+storage:
+  # NOTE(ericchiang): This will be replaced by sqlite3 in the future.
+  type: memory
+
+web:
+  http: 127.0.0.1:5556
+
+connectors:
+- type: mock
+  id: mock
+  name: Mock
+- type: github
+  id: github
+  name: GitHub
+  config:
+    clientID: "$GITHUB_CLIENT_ID"
+    clientSecret: "$GITHUB_CLIENT_SECRET"
+    redirectURI: http://127.0.0.1:5556/callback/github
+    org: kubernetes
+
+staticClients:
+- id: example-app
+  redirectURIs:
+  - 'http://127.0.0.1:5555/callback'
+  name: 'Example App'
+  secret: ZXhhbXBsZS1hcHAtc2VjcmV0
diff --git a/server/handlers.go b/server/handlers.go
index 85214a41565e33ef0e4abbb35f06286dcd3b07ac..76bdbc5fb7982366d29aa5afbf1ef200f37568e4 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -2,6 +2,7 @@ package server
 
 import (
 	"encoding/json"
+	"errors"
 	"fmt"
 	"log"
 	"net/http"
@@ -125,7 +126,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
 	for id := range s.connectors {
 		connectorInfos[i] = connectorInfo{
 			DisplayName: id,
-			URL:         s.absPath("/auth", id) + "?state=" + state,
+			URL:         s.absPath("/auth", id),
 		}
 		i++
 	}
@@ -224,6 +225,9 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
 }
 
 func (s *Server) finalizeLogin(identity connector.Identity, authReqID, connectorID string, conn connector.Connector) (string, error) {
+	if authReqID == "" {
+		return "", errors.New("no auth request ID passed")
+	}
 	claims := storage.Claims{
 		UserID:        identity.UserID,
 		Username:      identity.Username,
diff --git a/storage/memory/memory.go b/storage/memory/memory.go
index 748e9528ec3c021268e8dc19fb4ddb25911da014..d4bb1bcd8620d8a1a2b0fc13a8bab5b5933de0c8 100644
--- a/storage/memory/memory.go
+++ b/storage/memory/memory.go
@@ -18,6 +18,18 @@ func New() storage.Storage {
 	}
 }
 
+// Config is an implementation of a storage configuration.
+//
+// TODO(ericchiang): Actually define a storage config interface and have registration.
+type Config struct {
+	// The in memory implementation has no config.
+}
+
+// Open always returns a new in memory storage.
+func (c *Config) Open() (storage.Storage, error) {
+	return New(), nil
+}
+
 type memStorage struct {
 	mu sync.Mutex
 
diff --git a/storage/storage.go b/storage/storage.go
index 88b115427e5b291c188ff192cadf4523d1378664..399b35cdd795b6f093958f5530e90b31eb92cb50 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -81,19 +81,19 @@ type Storage interface {
 //   * Trusted peers: https://developers.google.com/identity/protocols/CrossClientAuth
 //   * Public clients: https://developers.google.com/api-client-library/python/auth/installed-app
 type Client struct {
-	ID           string
-	Secret       string
-	RedirectURIs []string
+	ID           string   `json:"id" yaml:"id"`
+	Secret       string   `json:"secret" yaml:"secret"`
+	RedirectURIs []string `json:"redirectURIs" yaml:"redirectURIs"`
 
 	// TrustedPeers are a list of peers which can issue tokens on this client's behalf.
 	// Clients inherently trust themselves.
-	TrustedPeers []string
+	TrustedPeers []string `json:"trustedPeers" yaml:"trustedPeers"`
 
 	// Public clients must use either use a redirectURL 127.0.0.1:X or "urn:ietf:wg:oauth:2.0:oob"
-	Public bool
+	Public bool `json:"public" yaml:"public"`
 
-	Name    string
-	LogoURL string
+	Name    string `json:"name" yaml:"name"`
+	LogoURL string `json:"logoURL" yaml:"logoURL"`
 }
 
 // Claims represents the ID Token claims supported by the server.