diff --git a/Makefile b/Makefile
index d1e05ebc6f14a153693096a86b3e93b20a2c0fed..596d9350a186de6554a1e7a362cff0ea856842d6 100644
--- a/Makefile
+++ b/Makefile
@@ -50,7 +50,7 @@ install: build
 
 .PHONY: mod
 mod:
-	go mod tidy -compat=1.19
+	go mod tidy -compat=1.20
 
 # ------------------------------------------------------------------------------
 #  test
diff --git a/UPGRADING.md b/UPGRADING.md
new file mode 100644
index 0000000000000000000000000000000000000000..74d7f4a25471a6a66f91187d73ce8d7b41146520
--- /dev/null
+++ b/UPGRADING.md
@@ -0,0 +1,15 @@
+# Upgrading from 1.X to 2.0
+
+In 2.0 the options for configuring client side RDP settings have been removed in favor of template file.
+The template file is a RDP file that is used as a template for the connection. The template file is parsed 
+and a few settings are replaced to ensure the client can connect to the server and the correct domain is used.
+
+The format of the template file is as follows:
+
+```
+# <setting>:<type i or s>:<value>
+domain:s:testdomain
+connection type:i:2
+```
+
+The filename is set under `client > defaults`.
diff --git a/cmd/rdpgw/config/configuration.go b/cmd/rdpgw/config/configuration.go
index c0fb9cc59e1ec881bf14ae9f3ec44fc7fdcad145..525158b8b354251917dff8dd73d2a7b032b9c4fc 100644
--- a/cmd/rdpgw/config/configuration.go
+++ b/cmd/rdpgw/config/configuration.go
@@ -93,7 +93,6 @@ type ClientConfig struct {
 	// kept for backwards compatibility
 	UsernameTemplate string `koanf:"usernametemplate"`
 	SplitUserDomain  bool   `koanf:"splituserdomain"`
-	DefaultDomain    string `koanf:"defaultdomain"`
 }
 
 func ToCamel(s string) string {
diff --git a/cmd/rdpgw/main.go b/cmd/rdpgw/main.go
index 6aed44c0b0d4ef44ccd759b7f5ddc4750f45a714..bf44b7b256a73773e918758f8548b84196d29f42 100644
--- a/cmd/rdpgw/main.go
+++ b/cmd/rdpgw/main.go
@@ -110,7 +110,6 @@ func main() {
 		RdpOpts: web.RdpOpts{
 			UsernameTemplate: conf.Client.UsernameTemplate,
 			SplitUserDomain:  conf.Client.SplitUserDomain,
-			DefaultDomain:    conf.Client.DefaultDomain,
 		},
 		GatewayAddress: url,
 		TemplateFile:   conf.Client.Defaults,
diff --git a/cmd/rdpgw/config/parsers/rdp.go b/cmd/rdpgw/rdp/koanf/parsers/rdp/rdp.go
similarity index 98%
rename from cmd/rdpgw/config/parsers/rdp.go
rename to cmd/rdpgw/rdp/koanf/parsers/rdp/rdp.go
index 5f6d947fefbdd3145658201cf580a77d43cafcae..c9020041d966784ace57a83769b645e5957b7c0f 100644
--- a/cmd/rdpgw/config/parsers/rdp.go
+++ b/cmd/rdpgw/rdp/koanf/parsers/rdp/rdp.go
@@ -1,4 +1,4 @@
-package parsers
+package rdp
 
 import (
 	"bufio"
diff --git a/cmd/rdpgw/config/parsers/rdp_test.go b/cmd/rdpgw/rdp/koanf/parsers/rdp/rdp_test.go
similarity index 99%
rename from cmd/rdpgw/config/parsers/rdp_test.go
rename to cmd/rdpgw/rdp/koanf/parsers/rdp/rdp_test.go
index 73d3fe053cabcb7470403716d4b5c639bcb53882..c73563eeb705ba91fe0b80943e44707fae0eddcd 100644
--- a/cmd/rdpgw/config/parsers/rdp_test.go
+++ b/cmd/rdpgw/rdp/koanf/parsers/rdp/rdp_test.go
@@ -1,4 +1,4 @@
-package parsers
+package rdp
 
 import (
 	"github.com/stretchr/testify/assert"
diff --git a/cmd/rdpgw/rdp/rdp.go b/cmd/rdpgw/rdp/rdp.go
index fe0f29deb301c43ac66c5ab48b301050a0374241..3b5a6a4ac8e7622f86abcb087625a49c6a369569 100644
--- a/cmd/rdpgw/rdp/rdp.go
+++ b/cmd/rdpgw/rdp/rdp.go
@@ -3,7 +3,10 @@ package rdp
 import (
 	"errors"
 	"fmt"
+	"github.com/bolkedebruin/rdpgw/cmd/rdpgw/rdp/koanf/parsers/rdp"
 	"github.com/fatih/structs"
+	"github.com/knadh/koanf/providers/file"
+	"github.com/knadh/koanf/v2"
 	"log"
 	"reflect"
 	"strconv"
@@ -81,21 +84,38 @@ type RdpSettings struct {
 	RemoteApplicationProgram              string `rdp:"remoteapplicationprogram"`
 }
 
-type RdpBuilder struct {
+type Builder struct {
 	Settings RdpSettings
 }
 
-func NewRdp() *RdpBuilder {
+func NewBuilder() *Builder {
 	c := RdpSettings{}
 
 	initStruct(&c)
 
-	return &RdpBuilder{
+	return &Builder{
 		Settings: c,
 	}
 }
 
-func (rb *RdpBuilder) String() string {
+func NewBuilderFromFile(filename string) (*Builder, error) {
+	c := RdpSettings{}
+	initStruct(&c)
+
+	var k = koanf.New(".")
+	if err := k.Load(file.Provider(filename), rdp.Parser()); err != nil {
+		return nil, err
+	}
+	t := koanf.UnmarshalConf{Tag: "rdp"}
+	if err := k.UnmarshalWithConf("", &c, t); err != nil {
+		return nil, err
+	}
+	return &Builder{
+		Settings: c,
+	}, nil
+}
+
+func (rb *Builder) String() string {
 	var sb strings.Builder
 
 	addStructToString(rb.Settings, &sb)
diff --git a/cmd/rdpgw/rdp/rdp_test.go b/cmd/rdpgw/rdp/rdp_test.go
index fc29bcbe64956b3b41a983c5d5b6237c2f4bf590..8efb70e73222cddedb7e2831efdcecb51e0c2a78 100644
--- a/cmd/rdpgw/rdp/rdp_test.go
+++ b/cmd/rdpgw/rdp/rdp_test.go
@@ -11,7 +11,7 @@ const (
 )
 
 func TestRdpBuilder(t *testing.T) {
-	builder := NewRdp()
+	builder := NewBuilder()
 	builder.Settings.GatewayHostname = "my.yahoo.com"
 	builder.Settings.AutoReconnectionEnabled = true
 	builder.Settings.SmartSizing = true
diff --git a/cmd/rdpgw/transport/legacy.go b/cmd/rdpgw/transport/legacy.go
index cfce51778e7af1c78b408f610b651d33e6558082..4277e3f4dcda43b6ad88b10943614e21dac01578 100644
--- a/cmd/rdpgw/transport/legacy.go
+++ b/cmd/rdpgw/transport/legacy.go
@@ -2,9 +2,9 @@ package transport
 
 import (
 	"bufio"
+	"crypto/rand"
 	"errors"
 	"io"
-	"math/rand"
 	"net"
 	"net/http"
 	"net/http/httputil"
@@ -12,14 +12,14 @@ import (
 )
 
 const (
-	crlf               = "\r\n"
+	crlf   = "\r\n"
 	HttpOK = "HTTP/1.1 200 OK\r\n"
 )
 
 type LegacyPKT struct {
-	Conn net.Conn
+	Conn          net.Conn
 	ChunkedReader io.Reader
-	Writer *bufio.Writer
+	Writer        *bufio.Writer
 }
 
 func NewLegacy(w http.ResponseWriter) (*LegacyPKT, error) {
@@ -27,9 +27,9 @@ func NewLegacy(w http.ResponseWriter) (*LegacyPKT, error) {
 	if ok {
 		conn, rw, err := hj.Hijack()
 		l := &LegacyPKT{
-			Conn: conn,
+			Conn:          conn,
 			ChunkedReader: httputil.NewChunkedReader(rw.Reader),
-			Writer: rw.Writer,
+			Writer:        rw.Writer,
 		}
 		return l, err
 	}
@@ -37,7 +37,7 @@ func NewLegacy(w http.ResponseWriter) (*LegacyPKT, error) {
 	return nil, errors.New("cannot hijack connection")
 }
 
-func (t *LegacyPKT) ReadPacket() (n int, p []byte, err error){
+func (t *LegacyPKT) ReadPacket() (n int, p []byte, err error) {
 	buf := make([]byte, 4096) // bufio.defaultBufSize
 	n, err = t.ChunkedReader.Read(buf)
 	p = make([]byte, n)
diff --git a/cmd/rdpgw/web/oidc.go b/cmd/rdpgw/web/oidc.go
index 03cece1891713b0a535fc99909a70905b5b1cd75..8458341a62f0531e532b030ebb3ca339963c1585 100644
--- a/cmd/rdpgw/web/oidc.go
+++ b/cmd/rdpgw/web/oidc.go
@@ -1,13 +1,13 @@
 package web
 
 import (
+	"crypto/rand"
 	"encoding/hex"
 	"encoding/json"
 	"github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity"
 	"github.com/coreos/go-oidc/v3/oidc"
 	"github.com/patrickmn/go-cache"
 	"golang.org/x/oauth2"
-	"math/rand"
 	"net/http"
 	"time"
 )
@@ -116,7 +116,11 @@ func (h *OIDC) Authenticated(next http.Handler) http.Handler {
 
 		if !id.Authenticated() {
 			seed := make([]byte, 16)
-			rand.Read(seed)
+			_, err := rand.Read(seed)
+			if err != nil {
+				http.Error(w, err.Error(), http.StatusInternalServerError)
+				return
+			}
 			state := hex.EncodeToString(seed)
 			h.stateStore.Set(state, r.RequestURI, cache.DefaultExpiration)
 			http.Redirect(w, r, h.oAuth2Config.AuthCodeURL(state), http.StatusFound)
diff --git a/cmd/rdpgw/web/web.go b/cmd/rdpgw/web/web.go
index 4b8fe7801965db8e661d70c91b6b3ed8e66fd95a..97c6bcbbf08d1cf6c92375f3c976cfb8261d3519 100644
--- a/cmd/rdpgw/web/web.go
+++ b/cmd/rdpgw/web/web.go
@@ -2,17 +2,15 @@ package web
 
 import (
 	"context"
+	"crypto/rand"
 	"encoding/hex"
 	"errors"
 	"fmt"
-	"github.com/bolkedebruin/rdpgw/cmd/rdpgw/config/parsers"
 	"github.com/bolkedebruin/rdpgw/cmd/rdpgw/identity"
 	"github.com/bolkedebruin/rdpgw/cmd/rdpgw/rdp"
-	"github.com/knadh/koanf/providers/file"
-	"github.com/knadh/koanf/v2"
 	"hash/maphash"
 	"log"
-	"math/rand"
+	rnd "math/rand"
 	"net/http"
 	"net/url"
 	"strings"
@@ -37,12 +35,8 @@ type Config struct {
 }
 
 type RdpOpts struct {
-	UsernameTemplate    string
-	SplitUserDomain     bool
-	DefaultDomain       string
-	NetworkAutoDetect   int
-	BandwidthAutoDetect int
-	ConnectionType      int
+	UsernameTemplate string
+	SplitUserDomain  bool
 }
 
 type Handler struct {
@@ -78,7 +72,7 @@ func (c *Config) NewHandler() *Handler {
 }
 
 func (h *Handler) selectRandomHost() string {
-	r := rand.New(rand.NewSource(int64(new(maphash.Hash).Sum64())))
+	r := rnd.New(rnd.NewSource(int64(new(maphash.Hash).Sum64())))
 	host := h.hosts[r.Intn(len(h.hosts))]
 	return host
 }
@@ -154,7 +148,7 @@ func (h *Handler) HandleDownload(w http.ResponseWriter, r *http.Request) {
 
 	// split the username into user and domain
 	var user = id.UserName()
-	var domain = opts.DefaultDomain
+	var domain = ""
 	if opts.SplitUserDomain {
 		creds := strings.SplitN(id.UserName(), "@", 2)
 		user = creds[0]
@@ -178,6 +172,7 @@ func (h *Handler) HandleDownload(w http.ResponseWriter, r *http.Request) {
 	if err != nil {
 		log.Printf("Cannot generate PAA token for user %s due to %s", user, err)
 		http.Error(w, errors.New("unable to generate gateway credentials").Error(), http.StatusInternalServerError)
+		return
 	}
 
 	if h.enableUserToken {
@@ -185,31 +180,40 @@ func (h *Handler) HandleDownload(w http.ResponseWriter, r *http.Request) {
 		if err != nil {
 			log.Printf("Cannot generate token for user %s due to %s", user, err)
 			http.Error(w, errors.New("unable to generate gateway credentials").Error(), http.StatusInternalServerError)
+			return
 		}
 		render = strings.Replace(render, "{{ token }}", userToken, 1)
 	}
 
 	// authenticated
 	seed := make([]byte, 16)
-	rand.Read(seed)
+	_, err = rand.Read(seed)
+	if err != nil {
+		log.Printf("Cannot generate random seed due to %s", err)
+		http.Error(w, errors.New("unable to generate random sequence").Error(), http.StatusInternalServerError)
+		return
+	}
 	fn := hex.EncodeToString(seed) + ".rdp"
 
 	w.Header().Set("Content-Disposition", "attachment; filename="+fn)
 	w.Header().Set("Content-Type", "application/x-rdp")
 
-	d := rdp.NewRdp()
-
-	if h.rdpDefaults != "" {
-		var k = koanf.New(".")
-		if err := k.Load(file.Provider(h.rdpDefaults), parsers.Parser()); err != nil {
-			log.Fatalf("cannot load rdp template file from %s", h.rdpDefaults)
+	var d *rdp.Builder
+	if h.rdpDefaults == "" {
+		d = rdp.NewBuilder()
+	} else {
+		d, err = rdp.NewBuilderFromFile(h.rdpDefaults)
+		if err != nil {
+			log.Printf("Cannot load RDP template file %s due to %s", h.rdpDefaults, err)
+			http.Error(w, errors.New("unable to load RDP template").Error(), http.StatusInternalServerError)
+			return
 		}
-		tag := koanf.UnmarshalConf{Tag: "rdp"}
-		k.UnmarshalWithConf("", &d.Settings, tag)
 	}
 
 	d.Settings.Username = render
-	d.Settings.Domain = domain
+	if domain != "" {
+		d.Settings.Domain = domain
+	}
 	d.Settings.FullAddress = host
 	d.Settings.GatewayHostname = h.gatewayAddress.Host
 	d.Settings.GatewayCredentialsSource = rdp.SourceCookie
diff --git a/cmd/rdpgw/web/web_test.go b/cmd/rdpgw/web/web_test.go
index f57cebfa228a044b1596dff43486528f74be62ee..6c53ca032425ef647ff5c3d2f0529451f3e79955 100644
--- a/cmd/rdpgw/web/web_test.go
+++ b/cmd/rdpgw/web/web_test.go
@@ -8,6 +8,7 @@ import (
 	"net/http"
 	"net/http/httptest"
 	"net/url"
+	"os"
 	"strings"
 	"testing"
 )
@@ -171,6 +172,51 @@ func TestHandler_HandleDownload(t *testing.T) {
 
 }
 
+func TestHandler_HandleDownloadWithRdpTemplate(t *testing.T) {
+	f, err := os.CreateTemp("", "rdp")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(f.Name())
+
+	err = os.WriteFile(f.Name(), []byte("domain:s:testdomain\r\n"), 0644)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	req, err := http.NewRequest("GET", "/connect", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	rr := httptest.NewRecorder()
+	id := identity.NewUser()
+
+	id.SetUserName(testuser)
+	id.SetAuthenticated(true)
+
+	req = identity.AddToRequestCtx(id, req)
+
+	u, _ := url.Parse(gateway)
+	c := Config{
+		HostSelection:     "roundrobin",
+		Hosts:             hosts,
+		PAATokenGenerator: paaTokenMock,
+		GatewayAddress:    u,
+		RdpOpts:           RdpOpts{SplitUserDomain: true},
+		TemplateFile:      f.Name(),
+	}
+	h := c.NewHandler()
+
+	hh := http.HandlerFunc(h.HandleDownload)
+	hh.ServeHTTP(rr, req)
+
+	data := rdpToMap(strings.Split(rr.Body.String(), rdp.CRLF))
+	if data["domain"] != "testdomain" {
+		t.Errorf("domain key in rdp does not match: got %v want %v", data["domain"], "testdomain")
+	}
+}
+
 func paaTokenMock(ctx context.Context, username string, host string) (string, error) {
 	return username + "_" + host, nil
 }
diff --git a/dev/docker/rdpgw.yaml b/dev/docker/rdpgw.yaml
index ee53f9c4344e2aa3112bfa701e524ca0645ac88e..5d2cc59c855b7c6d6ae49803fe833f686d0ae41a 100644
--- a/dev/docker/rdpgw.yaml
+++ b/dev/docker/rdpgw.yaml
@@ -14,9 +14,6 @@ OpenId:
  ClientSecret: 01cd304c-6f43-4480-9479-618eb6fd578f
 Client:
  UsernameTemplate: "{{ username }}"
- NetworkAutoDetect: 0
- BandwidthAutoDetect: 1
- ConnectionType: 6
 Security:
   PAATokenSigningKey: prettypleasereplacemeinproductio
 Caps: