diff --git a/cmd/xconn/authenticator.go b/cmd/xconn/authenticator.go
new file mode 100644
index 0000000..d406473
--- /dev/null
+++ b/cmd/xconn/authenticator.go
@@ -0,0 +1,71 @@
+package main
+
+import (
+	"fmt"
+
+	"golang.org/x/exp/slices"
+
+	"github.com/xconnio/wampproto-go/auth"
+)
+
+type Authenticator struct {
+	authenticator Authenticators
+}
+
+func NewAuthenticator(authenticators Authenticators) *Authenticator {
+	return &Authenticator{authenticator: authenticators}
+}
+
+func (a *Authenticator) Methods() []auth.Method {
+	return []auth.Method{auth.MethodAnonymous, auth.MethodTicket, auth.MethodCRA, auth.MethodCryptoSign}
+}
+
+func (a *Authenticator) Authenticate(request auth.Request) (auth.Response, error) {
+	switch request.AuthMethod() {
+	case auth.MethodAnonymous:
+		for _, anonymous := range a.authenticator.Anonymous {
+			if anonymous.Realm == request.Realm() {
+				return auth.NewResponse(request.AuthID(), request.AuthRole(), 0)
+			}
+		}
+		return nil, fmt.Errorf("invalid realm")
+
+	case auth.MethodTicket:
+		ticketRequest, ok := request.(*auth.TicketRequest)
+		if !ok {
+			return nil, fmt.Errorf("invalid request")
+		}
+
+		for _, ticket := range a.authenticator.Ticket {
+			if ticket.Realm == ticketRequest.Realm() && ticket.Ticket == ticketRequest.Ticket() {
+				return auth.NewResponse(ticketRequest.AuthID(), ticketRequest.AuthRole(), 0)
+			}
+		}
+		return nil, fmt.Errorf("invalid ticket")
+
+	case auth.MethodCRA:
+		for _, wampcra := range a.authenticator.WAMPCRA {
+			if wampcra.Realm == request.Realm() {
+				return auth.NewCRAResponse(request.AuthID(), request.AuthRole(), wampcra.Secret, 0), nil
+			}
+		}
+		return nil, fmt.Errorf("invalid realm")
+
+	case auth.MethodCryptoSign:
+		cryptosignRequest, ok := request.(*auth.RequestCryptoSign)
+		if !ok {
+			return nil, fmt.Errorf("invalid request")
+		}
+
+		for _, cryptosign := range a.authenticator.CryptoSign {
+			if cryptosign.Realm == cryptosignRequest.Realm() &&
+				slices.Contains(cryptosign.AuthorizedKeys, cryptosignRequest.PublicKey()) {
+				return auth.NewResponse(cryptosignRequest.AuthID(), cryptosignRequest.AuthRole(), 0)
+			}
+		}
+		return nil, fmt.Errorf("unknown publickey")
+
+	default:
+		return nil, fmt.Errorf("unknown authentication method: %v", request.AuthMethod())
+	}
+}
diff --git a/cmd/xconn/config.yaml.in b/cmd/xconn/config.yaml.in
new file mode 100644
index 0000000..aaf1031
--- /dev/null
+++ b/cmd/xconn/config.yaml.in
@@ -0,0 +1,37 @@
+version: '1'
+
+realms:
+  - name: realm1
+
+transports:
+  - type: websocket
+    port: 8080
+    serializers:
+      - json
+      - cbor
+      - protobuf
+
+authenticators:
+  cryptosign:
+    - authid: john
+      realm: realm1
+      role: anonymous
+      authorized_keys:
+        - 20e6ff0eb2552204fac19a15a61da586e437abd64a545bedce61a89b48184fcb
+
+  wampcra:
+    - authid: john
+      realm: realm1
+      role: anonymous
+      secret: hello
+
+  ticket:
+    - authid: john
+      realm: realm1
+      role: anonymous
+      ticket: hello
+
+  anonymous:
+    - authid: john
+      realm: realm1
+      role: anonymous
diff --git a/cmd/xconn/main.go b/cmd/xconn/main.go
index f38dc68..efbc787 100644
--- a/cmd/xconn/main.go
+++ b/cmd/xconn/main.go
@@ -1,24 +1,121 @@
 package main
 
 import (
+	"bytes"
+	_ "embed" // nolint:gci
+	"fmt"
 	"log"
+	"os"
+
+	"github.com/alecthomas/kingpin/v2"
+	"golang.org/x/exp/slices"
+	"gopkg.in/yaml.v3"
 
 	"github.com/xconnio/wampproto-protobuf/go"
 	"github.com/xconnio/xconn-go"
 )
 
-func main() {
-	router := xconn.NewRouter()
-	router.AddRealm("realm1")
+var (
+	//go:embed config.yaml.in
+	sampleConfig []byte
+)
 
-	server := xconn.NewServer(router, nil)
+const (
+	versionString = "0.1.0"
 
-	serializer := &wampprotobuf.ProtobufSerializer{}
-	protobufSpec := xconn.NewWSSerializerSpec("wamp.2.protobuf", serializer)
+	ConfigDir  = ".xconn"
+	ConfigFile = ConfigDir + "/config.yaml"
+
+	ProtobufSubProtocol = "wamp.2.protobuf"
+)
+
+type cmd struct {
+	parsedCommand string
+
+	init *kingpin.CmdClause
+
+	start *kingpin.CmdClause
+}
 
-	if err := server.RegisterSpec(protobufSpec); err != nil {
-		log.Fatal(err)
+func parseCommand(args []string) (*cmd, error) {
+	app := kingpin.New(args[0], "XConn")
+	app.Version(versionString).VersionFlag.Short('v')
+
+	c := &cmd{
+		init:  app.Command("init", "Initialize sample router config."),
+		start: app.Command("start", "Start the router."),
+	}
+
+	parsedCommand, err := app.Parse(args[1:])
+	if err != nil {
+		return nil, err
+	}
+	c.parsedCommand = parsedCommand
+
+	return c, nil
+}
+
+func Run(args []string) error {
+	c, err := parseCommand(args)
+	if err != nil {
+		return err
 	}
 
-	log.Fatal(server.Start("0.0.0.0", 8080))
+	switch c.parsedCommand {
+	case c.init.FullCommand():
+		if err := os.MkdirAll(ConfigDir, os.ModePerm); err != nil {
+			return err
+		}
+
+		if err = os.WriteFile(ConfigFile, sampleConfig, 0600); err != nil {
+			return fmt.Errorf("unable to write config: %w", err)
+		}
+
+	case c.start.FullCommand():
+		data, err := os.ReadFile(ConfigFile)
+		if err != nil {
+			return fmt.Errorf("unable to read config file: %w", err)
+		}
+
+		var decoder = yaml.NewDecoder(bytes.NewBuffer(data))
+		decoder.KnownFields(true)
+
+		var config Config
+		if err := decoder.Decode(&config); err != nil {
+			return fmt.Errorf("unable to decode config file: %w", err)
+		}
+
+		router := xconn.NewRouter()
+
+		for _, realm := range config.Realms {
+			router.AddRealm(realm.Name)
+		}
+
+		authenticator := NewAuthenticator(config.Authenticators)
+		server := xconn.NewServer(router, authenticator)
+
+		for _, transport := range config.Transports {
+			if slices.Contains(transport.Serializers, "protobuf") {
+				serializer := &wampprotobuf.ProtobufSerializer{}
+				protobufSpec := xconn.NewWSSerializerSpec(ProtobufSubProtocol, serializer)
+
+				if err := server.RegisterSpec(protobufSpec); err != nil {
+					return err
+				}
+			}
+
+			if err := server.Start("0.0.0.0", transport.Port); err != nil {
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+func main() {
+	if err := Run(os.Args); err != nil {
+		log.Fatalln(err)
+	}
 }
diff --git a/cmd/xconn/types.go b/cmd/xconn/types.go
new file mode 100644
index 0000000..bef56c1
--- /dev/null
+++ b/cmd/xconn/types.go
@@ -0,0 +1,55 @@
+package main
+
+type Config struct {
+	Version        string         `yaml:"version"`
+	Realms         []Realm        `yaml:"realms"`
+	Transports     []Transport    `yaml:"transports"`
+	Authenticators Authenticators `yaml:"authenticators"`
+}
+
+type Realm struct {
+	Name string `yaml:"name"`
+}
+
+type Transport struct {
+	Type        string   `yaml:"type"`
+	Port        int      `yaml:"port"`
+	Serializers []string `yaml:"serializers"`
+}
+
+type CryptoSign struct {
+	AuthID         string   `yaml:"authid"`
+	Realm          string   `yaml:"realm"`
+	Role           string   `yaml:"role"`
+	AuthorizedKeys []string `yaml:"authorized_keys"`
+}
+
+type WAMPCRA struct {
+	AuthID     string `yaml:"authid"`
+	Realm      string `yaml:"realm"`
+	Role       string `yaml:"role"`
+	Secret     string `yaml:"secret"`
+	Salt       string `yaml:"salt"`
+	Iterations int    `yaml:"iterations"`
+	KeyLen     int    `yaml:"keylen"`
+}
+
+type Ticket struct {
+	AuthID string `yaml:"authid"`
+	Realm  string `yaml:"realm"`
+	Role   string `yaml:"role"`
+	Ticket string `yaml:"ticket"`
+}
+
+type Anonymous struct {
+	AuthID string `yaml:"authid"`
+	Realm  string `yaml:"realm"`
+	Role   string `yaml:"role"`
+}
+
+type Authenticators struct {
+	CryptoSign []CryptoSign `yaml:"cryptosign"`
+	WAMPCRA    []WAMPCRA    `yaml:"wampcra"`
+	Ticket     []Ticket     `yaml:"ticket"`
+	Anonymous  []Anonymous  `yaml:"anonymous"`
+}
diff --git a/go.mod b/go.mod
index b107ab4..4162356 100644
--- a/go.mod
+++ b/go.mod
@@ -3,16 +3,19 @@ module github.com/xconnio/xconn-go
 go 1.20
 
 require (
+	github.com/alecthomas/kingpin/v2 v2.4.0
 	github.com/gammazero/nexus/v3 v3.2.2
 	github.com/gammazero/workerpool v1.1.3
 	github.com/gobwas/ws v1.4.0
 	github.com/stretchr/testify v1.8.4
-	github.com/xconnio/wampproto-go v0.0.0-20240606221544-14a7a1771d92
-	github.com/xconnio/wampproto-protobuf/go v0.0.0-20240607222929-52f6a1b22c87
+	github.com/xconnio/wampproto-go v0.0.0-20240611083555-4bcae9e441a3
+	github.com/xconnio/wampproto-protobuf/go v0.0.0-20240611092706-1e859744b5a2
 	golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d
+	gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
+	github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/fxamacker/cbor/v2 v2.6.0 // indirect
 	github.com/gammazero/deque v0.2.0 // indirect
@@ -24,8 +27,8 @@ require (
 	github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
+	github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
 	golang.org/x/crypto v0.23.0 // indirect
 	golang.org/x/sys v0.20.0 // indirect
 	google.golang.org/protobuf v1.34.1 // indirect
-	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 473900c..20d7b79 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,8 @@
+github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY=
+github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
+github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
+github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
@@ -19,6 +24,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
 github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
@@ -29,10 +36,12 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
 github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
-github.com/xconnio/wampproto-go v0.0.0-20240606221544-14a7a1771d92 h1:sQGmy7DEnqegkRvLqWNkz+BY7n1DiisfptURHk7Nh30=
-github.com/xconnio/wampproto-go v0.0.0-20240606221544-14a7a1771d92/go.mod h1:/b7EyR1X9EkOHPQBJGz1KvdjClo1GsalBGIzjQU5+i4=
-github.com/xconnio/wampproto-protobuf/go v0.0.0-20240607222929-52f6a1b22c87 h1:m7533sneh3nWbdPgWbQqkAOEZO2WSkdjaSO9rQZzTmY=
-github.com/xconnio/wampproto-protobuf/go v0.0.0-20240607222929-52f6a1b22c87/go.mod h1:4mNLdXqx+2F43CRWaYqaMwZw0moLX3UURbFnaaCRtI8=
+github.com/xconnio/wampproto-go v0.0.0-20240611083555-4bcae9e441a3 h1:iVd1el18AP1N3SnZy06ivkKcHtTuTD7cg3JhUM+xQbU=
+github.com/xconnio/wampproto-go v0.0.0-20240611083555-4bcae9e441a3/go.mod h1:/b7EyR1X9EkOHPQBJGz1KvdjClo1GsalBGIzjQU5+i4=
+github.com/xconnio/wampproto-protobuf/go v0.0.0-20240611092706-1e859744b5a2 h1:1WkQ68ICoin0wTerMn5FrWl+ZbK4XS3/W2Wr86jS9K8=
+github.com/xconnio/wampproto-protobuf/go v0.0.0-20240611092706-1e859744b5a2/go.mod h1:SZAkbKSDucIUBrPgcKlT0SzVmktfrsD7OdP1chFR+vw=
+github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
+github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
 go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
 golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
 golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
@@ -45,5 +54,6 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW
 google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=