Repository: nats-io/nats-server
Pull request #1463 (2020-06-09 17:51:38)

Added dedicated auth block for websocket
Websocket can now override
- Username/password
- Token
- Users
- NKeys
- no_auth_user
- auth_timeout

For TLS, support for verify and verify_and_map. We used to set
tls config's ClientAuth to NoClientCert. It will now depend
if the config requires client certificate verification, which
is needed if TLSMap is enabled.

Signed-off-by: Ivan Kozlovic <ivan@synadia.com>



Patch:
diff --git a/go.mod b/go.mod
index 9735fb01..96c06cb0 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/nats-io/nats-server/v2
 require (
 	github.com/minio/highwayhash v1.0.0
 	github.com/nats-io/jwt/v2 v2.0.0-20200602193336-473d698956ed
-	github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a
+	github.com/nats-io/nats.go v1.10.1-0.20200601214746-e93e18d0ed6f
 	github.com/nats-io/nkeys v0.2.0
 	github.com/nats-io/nuid v1.0.1
 	golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
diff --git a/go.sum b/go.sum
index 43fe0de5..b5e683be 100644
--- a/go.sum
+++ b/go.sum
@@ -20,8 +20,8 @@ github.com/nats-io/nats-server/v2 v2.1.8-0.20200524125952-51ebd92a9093/go.mod h1
 github.com/nats-io/nats-server/v2 v2.1.8-0.20200601203034-f8d6dd992b71/go.mod h1:Nan/1L5Sa1JRW+Thm4HNYcIDcVRFc5zK9OpSZeI2kk4=
 github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE=
 github.com/nats-io/nats.go v1.10.1-0.20200531124210-96f2130e4d55/go.mod h1:ARiFsjW9DVxk48WJbO3OSZ2DG8fjkMi7ecLmXoY/n9I=
-github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a h1:gzSKZOBlu/DpbuPbt34paXCOvA6+E+lVfU2BmomQ9HA=
-github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a/go.mod h1:8eAIv96Mo9QW6Or40jUHejS7e4VwZ3VRYD6Sf0BTDp4=
+github.com/nats-io/nats.go v1.10.1-0.20200601214746-e93e18d0ed6f h1:ILS2coqvxP2B/pptNWB5xLu626mIiflILpKwEvma9eQ=
+github.com/nats-io/nats.go v1.10.1-0.20200601214746-e93e18d0ed6f/go.mod h1:8eAIv96Mo9QW6Or40jUHejS7e4VwZ3VRYD6Sf0BTDp4=
 github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
 github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
 github.com/nats-io/nkeys v0.2.0 h1:WXKF7diOaPU9cJdLD7nuzwasQy9vT1tBqzXZZf3AMJM=
diff --git a/server/auth.go b/server/auth.go
index 8f60bf8f..eb4c3b58 100644
--- a/server/auth.go
+++ b/server/auth.go
@@ -278,6 +278,9 @@ func (s *Server) configureAuthorization() {
 		s.users = nil
 		s.info.AuthRequired = false
 	}
+
+	// Do similar for websocket config
+	s.wsConfigAuth(&opts.Websocket)
 }
 
 // checkAuthentication will check based on client type and
@@ -309,10 +312,64 @@ func (s *Server) isClientAuthorized(c *client) bool {
 		return opts.CustomClientAuthentication.Check(c)
 	}
 
-	return s.processClientOrLeafAuthentication(c)
+	return s.processClientOrLeafAuthentication(c, opts)
+}
+
+type authOpts struct {
+	username   string
+	password   string
+	token      string
+	noAuthUser string
+	tlsMap     bool
+	users      map[string]*User
+	nkeys      map[string]*NkeyUser
 }
 
-func (s *Server) processClientOrLeafAuthentication(c *client) bool {
+func (s *Server) getAuthOpts(c *client, o *Options, auth *authOpts) bool {
+	// c.ws is immutable, but may need lock if we get race reports.
+	wsClient := c.ws != nil
+
+	authRequired := s.info.AuthRequired
+	// For websocket clients, if there is no top-level auth, then we
+	// check for websocket specifically.
+	if !authRequired && wsClient {
+		authRequired = s.websocket.authRequired
+	}
+	if !authRequired {
+		return false
+	}
+	auth.noAuthUser = o.NoAuthUser
+	auth.tlsMap = o.TLSMap
+	if wsClient {
+		wo := &o.Websocket
+		// If those are specified, override, regardless if there is
+		// auth configuration (like user/pwd, etc..) in websocket section.
+		if wo.NoAuthUser != _EMPTY_ {
+			auth.noAuthUser = wo.NoAuthUser
+		}
+		if wo.TLSMap {
+			auth.tlsMap = true
+		}
+		// Now check for websocket auth specific override
+		if s.websocket.authRequired {
+			auth.username = wo.Username
+			auth.password = wo.Password
+			auth.token = wo.Token
+			auth.users = s.websocket.users
+			auth.nkeys = s.websocket.nkeys
+			return true
+		}
+		// else fallback to regular auth config
+	}
+	auth.username = o.Username
+	auth.password = o.Password
+	auth.token = o.Authorization
+	auth.users = s.users
+	auth.nkeys = s.nkeys
+	return true
+}
+
+func (s *Server) processClientOrLeafAuthentication(c *client, opts *Options) bool {
 	var (
 		nkey *NkeyUser
 		juc  *jwt.UserClaims
@@ -320,11 +377,11 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
 		user *User
 		ok   bool
 		err  error
-		opts = s.getOpts()
+		auth authOpts
 	)
 
 	s.mu.Lock()
-	authRequired := s.info.AuthRequired
+	authRequired := s.getAuthOpts(c, opts, &auth)
 	if !authRequired {
 		// TODO(dlc) - If they send us credentials should we fail?
 		s.mu.Unlock()
@@ -355,21 +412,21 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
 	}
 
 	// Check if we have nkeys or users for client.
-	hasNkeys := s.nkeys != nil
-	hasUsers := s.users != nil
+	hasNkeys := auth.nkeys != nil
+	hasUsers := auth.users != nil
 	if hasNkeys && c.opts.Nkey != "" {
-		nkey, ok = s.nkeys[c.opts.Nkey]
+		nkey, ok = auth.nkeys[c.opts.Nkey]
 		if !ok {
 			s.mu.Unlock()
 			return false
 		}
 	} else if hasUsers {
 		// Check if we are tls verify and are mapping users from the client_certificate
-		if opts.TLSMap {
+		if auth.tlsMap {
 			var euser string
 			authorized := checkClientTLSCertSubject(c, func(u string) bool {
 				var ok bool
-				user, ok = s.users[u]
+				user, ok = auth.users[u]
 				if !ok {
 					c.Debugf("User in cert [%q], not found", u)
 					return false
@@ -388,14 +445,14 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
 			// but we set it here to be able to identify it in the logs.
 			c.opts.Username = euser
 		} else {
-			if c.kind == CLIENT && c.opts.Username == "" && s.opts.NoAuthUser != "" {
-				if u, exists := s.users[s.opts.NoAuthUser]; exists {
+			if c.kind == CLIENT && c.opts.Username == "" && auth.noAuthUser != "" {
+				if u, exists := auth.users[auth.noAuthUser]; exists {
 					c.opts.Username = u.Username
 					c.opts.Password = u.Password
 				}
 			}
 			if c.opts.Username != "" {
-				user, ok = s.users[c.opts.Username]
+				user, ok = auth.users[c.opts.Username]
 				if !ok {
 					s.mu.Unlock()
 					return false
@@ -517,13 +574,13 @@ func (s *Server) processClientOrLeafAuthentication(c *client) bool {
 	}
 
 	if c.kind == CLIENT {
-		if opts.Authorization != "" {
-			return comparePasswords(opts.Authorization, c.opts.Token)
-		} else if opts.Username != "" {
-			if opts.Username != c.opts.Username {
+		if auth.token != "" {
+			return comparePasswords(auth.token, c.opts.Token)
+		} else if auth.username != "" {
+			if auth.username != c.opts.Username {
 				return false
 			}
-			return comparePasswords(opts.Password, c.opts.Password)
+			return comparePasswords(auth.password, c.opts.Password)
 		}
 	} else if c.kind == LEAF {
 		// There is no required username/password to connect and
@@ -733,7 +790,7 @@ func (s *Server) isLeafNodeAuthorized(c *client) bool {
 	// Still, if the CONNECT has some user info, we will bind to the
 	// user's account or to the specified default account (if provided)
 	// or to the global account.
-	return s.processClientOrLeafAuthentication(c)
+	return s.processClientOrLeafAuthentication(c, opts)
 }
 
 // Support for bcrypt stored passwords and tokens.
diff --git a/server/client.go b/server/client.go
index 4298b015..47d9253b 100644
--- a/server/client.go
+++ b/server/client.go
@@ -868,7 +868,7 @@ func (c *client) flushClients(budget time.Duration) time.Time {
 
 // readLoop is the main socket read functionality.
 // Runs in its own Go routine.
-func (c *client) readLoop(pre []byte) {
+func (c *client) readLoop() {
 	// Grab the connection off the client, it will be cleared on a close.
 	// We check for that after the loop, but want to avoid a nil dereference
 	c.mu.Lock()
@@ -918,11 +918,6 @@ func (c *client) readLoop(pre []byte) {
 		wsr.init()
 	}
 
-	// If we have a pre parse that first.
-	if len(pre) > 0 {
-		c.parse(pre)
-	}
-
 	for {
 		n, err := nc.Read(b)
 		// If we have any data we will try to parse and exit at the end.
diff --git a/server/const.go b/server/const.go
index f590a3cc..35a8c853 100644
--- a/server/const.go
+++ b/server/const.go
@@ -146,10 +146,6 @@ const (
 	// the closing of clients when signaled to go in lame duck mode.
 	DEFAULT_LAME_DUCK_DURATION = 2 * time.Minute
 
-	// DEFAULT_LAME_DUCK_GRACE_PERIOD is the duration the server waits, after entering
-	// lame duck mode, before starting closing client connections.
-	DEFAULT_LAME_DUCK_GRACE_PERIOD = 10 * time.Second
-
 	// DEFAULT_LEAFNODE_INFO_WAIT Route dial timeout.
 	DEFAULT_LEAFNODE_INFO_WAIT = 1 * time.Second
 
diff --git a/server/gateway.go b/server/gateway.go
index 08622c81..acae75c1 100644
--- a/server/gateway.go
+++ b/server/gateway.go
@@ -846,7 +846,7 @@ func (s *Server) createGateway(cfg *gatewayCfg, url *url.URL, conn net.Conn) {
 	}
 
 	// Spin up the read loop.
-	s.startGoRoutine(func() { c.readLoop(nil) })
+	s.startGoRoutine(func() { c.readLoop() })
 
 	// Spin up the write loop.
 	s.startGoRoutine(func() { c.writeLoop() })
diff --git a/server/leafnode.go b/server/leafnode.go
index 2305dccf..e72d4d37 100644
--- a/server/leafnode.go
+++ b/server/leafnode.go
@@ -813,7 +813,7 @@ func (s *Server) createLeafNode(conn net.Conn, remote *leafNodeCfg) *client {
 	}
 
 	// Spin up the read loop.
-	s.startGoRoutine(func() { c.readLoop(nil) })
+	s.startGoRoutine(func() { c.readLoop() })
 
 	// Spin up the write loop.
 	s.startGoRoutine(func() { c.writeLoop() })
diff --git a/server/opts.go b/server/opts.go
index 21e6712d..2702b484 100644
--- a/server/opts.go
+++ b/server/opts.go
@@ -208,11 +208,9 @@ type Options struct {
 	TLSKey                string        `json:"-"`
 	TLSCaCert             string        `json:"-"`
 	TLSConfig             *tls.Config   `json:"-"`
-	AllowNonTLS           bool          `json:"-"`
 	WriteDeadline         time.Duration `json:"-"`
 	MaxClosedClients      int           `json:"-"`
 	LameDuckDuration      time.Duration `json:"-"`
-	LameDuckGracePeriod   time.Duration `json:"-"`
 
 	// MaxTracedMsgLen is the maximum printable length for traced messages.
 	MaxTracedMsgLen int `json:"-"`
@@ -259,8 +257,26 @@ type WebsocketOpts struct {
 	// The host:port to advertise to websocket clients in the cluster.
 	Advertise string
 
+	// If no user is provided when a client connects, will default to this
+	// user and associated account. This user has to exist either in the
+	// Users defined here or in the global options.
+	NoAuthUser string
+
+	// Authentication section. If anything is configured in this section,
+	// it will override the authorization configuration for regular clients.
+	Username string
+	Password string
+	Token    string
+	Users    []*User
+	Nkeys    []*NkeyUser
+
+	// Timeout for the authentication process.
+	AuthTimeout float64
+
 	// TLS configuration is required.
 	TLSConfig *tls.Config
+	// If true, map certificate values for authentication purposes.
+	TLSMap bool
 
 	// If true, the Origin header must match the request's host.
 	SameOrigin bool
@@ -716,9 +732,6 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
 		}
 		o.TLSTimeout = tc.Timeout
 		o.TLSMap = tc.Map
-
-	case "allow_non_tls":
-		o.AllowNonTLS = v.(bool)
 	case "write_deadline":
 		o.WriteDeadline = parseDuration("write_deadline", tk, v, errors, warnings)
 	case "lame_duck_duration":
@@ -734,19 +747,6 @@ func (o *Options) processConfigFileLine(k string, v interface{}, errors *[]error
 			return
 		}
 		o.LameDuckDuration = dur
-	case "lame_duck_grace_period":
-		dur, err := time.ParseDuration(v.(string))
-		if err != nil {
-			err := &configErr{tk, fmt.Sprintf("error parsing lame_duck_grace_period: %v", err)}
-			*errors = append(*errors, err)
-			return
-		}
-		if dur < 0 {
-			err := &configErr{tk, "invalid lame_duck_grace_period, needs to be positive"}
-			*errors = append(*errors, err)
-			return
-		}
-		o.LameDuckGracePeriod = dur
 	case "operator", "operators", "roots", "root", "root_operators", "root_operator":
 		opFiles := []string{}
 		switch v := v.(type) {
@@ -3043,12 +3043,17 @@ func parseWebsocket(v interface{}, o *Options, errors *[]error, warnings *[]erro
 		case "advertise":
 			o.Websocket.Advertise = mv.(string)
 		case "tls":
-			config, _, err := getTLSConfig(tk)
+			tc, err := parseTLS(tk)
 			if err != nil {
 				*errors = append(*errors, err)
 				continue
 			}
-			o.Websocket.TLSConfig = config
+			if o.Websocket.TLSConfig, err = GenTLSConfig(tc); err != nil {
+				err := &configErr{tk, err.Error()}
+				*errors = append(*errors, err)
+				continue
+			}
+			o.Websocket.TLSMap = tc.Map
 		case "same_origin":
 			o.Websocket.SameOrigin = mv.(bool)
 		case "allowed_origins", "allowed_origin", "allow_origins", "allow_origin", "origins", "origin":
@@ -3092,6 +3097,42 @@ func parseWebsocket(v interface{}, o *Options, errors *[]error, warnings *[]erro
 			o.Websocket.HandshakeTimeout = ht
 		case "compression":
 			o.Websocket.Compression = mv.(bool)
+		case "authorization", "authentication":
+			auth, err := parseAuthorization(tk, o, errors, warnings)
+			if err != nil {
+				*errors = append(*errors, err)
+				continue
+			}
+			o.Websocket.Username = auth.user
+			o.Websocket.Password = auth.pass
+			o.Websocket.Token = auth.token
+			if (auth.user != "" || auth.pass != "") && auth.token != "" {
+				err := &configErr{tk, "Cannot have a user/pass and token"}
+				*errors = append(*errors, err)
+				continue
+			}
+			o.Websocket.AuthTimeout = auth.timeout
+			// Check for multiple users defined
+			if auth.users != nil {
+				if auth.user != "" {
+					err := &configErr{tk, "Can not have a single user/pass and a users array"}
+					*errors = append(*errors, err)
+					continue
+				}
+				if auth.token != "" {
+					err := &configErr{tk, "Can not have a token and a users array"}
+					*errors = append(*errors, err)
+					continue
+				}
+				// Users may have been added from Accounts parsing, so do an append here
+				o.Websocket.Users = append(o.Websocket.Users, auth.users...)
+			}
+			// Check for nkeys
+			if auth.nkeys != nil {
+				o.Websocket.Nkeys = append(o.Websocket.Nkeys, auth.nkeys...)
+			}
+		case "no_auth_user":
+			o.Websocket.NoAuthUser = mv.(string)
 		default:
 			if !tk.IsUsedVariable() {
 				err := &unknownConfigFieldErr{
@@ -3426,9 +3467,6 @@ func setBaselineOptions(opts *Options) {
 	if opts.LameDuckDuration == 0 {
 		opts.LameDuckDuration = DEFAULT_LAME_DUCK_DURATION
 	}
-	if opts.LameDuckGracePeriod == 0 {
-		opts.LameDuckGracePeriod = DEFAULT_LAME_DUCK_GRACE_PERIOD
-	}
 	if opts.Gateway.Port != 0 {
 		if opts.Gateway.Host == "" {
 			opts.Gateway.Host = DEFAULT_HOST
diff --git a/server/route.go b/server/route.go
index 5b20501d..0a69a74b 100644
--- a/server/route.go
+++ b/server/route.go
@@ -1222,7 +1222,7 @@ func (s *Server) createRoute(conn net.Conn, rURL *url.URL) *client {
 	}
 
 	// Spin up the read loop.
-	s.startGoRoutine(func() { c.readLoop(nil) })
+	s.startGoRoutine(func() { c.readLoop() })
 
 	// Spin up the write loop.
 	s.startGoRoutine(func() { c.writeLoop() })
diff --git a/server/server.go b/server/server.go
index 7f65e18d..53f84839 100644
--- a/server/server.go
+++ b/server/server.go
@@ -20,7 +20,6 @@ import (
 	"encoding/json"
 	"flag"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"math/rand"
 	"net"
@@ -46,6 +45,9 @@ import (
 )
 
 const (
+	// Time to wait before starting closing clients when in LD mode.
+	lameDuckModeDefaultInitialDelay = int64(10 * time.Second)
+
 	// Interval for the first PING for non client connections.
 	firstPingInterval = time.Second
 
@@ -53,6 +55,9 @@ const (
 	firstClientPingInterval = 2 * time.Second
 )
 
+// Make this a variable so that we can change during tests
+var lameDuckModeInitialDelay = int64(lameDuckModeDefaultInitialDelay)
+
 // Info is the information sent to clients, routes, gateways, and leaf nodes,
 // to help them understand information about this server.
 type Info struct {
@@ -68,7 +73,6 @@ type Info struct {
 	AuthRequired      bool     `json:"auth_required,omitempty"`
 	TLSRequired       bool     `json:"tls_required,omitempty"`
 	TLSVerify         bool     `json:"tls_verify,omitempty"`
-	TLSAvailable      bool     `json:"tls_available,omitempty"`
 	MaxPayload        int32    `json:"max_payload"`
 	JetStream         bool     `json:"jetstream,omitempty"`
 	IP                string   `json:"ip,omitempty"`
@@ -271,17 +275,13 @@ func NewServer(opts *Options) (*Server, error) {
 		Host:         opts.Host,
 		Port:         opts.Port,
 		AuthRequired: false,
-		TLSRequired:  tlsReq && !opts.AllowNonTLS,
+		TLSRequired:  tlsReq,
 		TLSVerify:    verify,
 		MaxPayload:   opts.MaxPayload,
 		JetStream:    opts.JetStream,
 		Headers:      !opts.NoHeaderSupport,
 	}
 
-	if tlsReq && !info.TLSRequired {
-		info.TLSAvailable = true
-	}
-
 	now := time.Now()
 
 	s := &Server{
@@ -427,10 +427,6 @@ func (s *Server) ClientURL() string {
 }
 
 func validateOptions(o *Options) error {
-	if o.LameDuckDuration > 0 && o.LameDuckGracePeriod >= o.LameDuckDuration {
-		return fmt.Errorf("lame duck grace period (%v) should be strictly lower than lame duck duration (%v)",
-			o.LameDuckGracePeriod, o.LameDuckDuration)
-	}
 	// Check that the trust configuration is correct.
 	if err := validateTrustedOperators(o); err != nil {
 		return err
@@ -1860,24 +1856,6 @@ func (s *Server) copyInfo() Info {
 	return info
 }
 
-// tlsMixConn is used when we can receive both TLS and non-TLS connections on same port.
-type tlsMixConn struct {
-	net.Conn
-	pre *bytes.Buffer
-}
-
-// Read for our mixed multi-reader.
-func (c *tlsMixConn) Read(b []byte) (int, error) {
-	if c.pre != nil {
-		n, err := c.pre.Read(b)
-		if c.pre.Len() == 0 {
-			c.pre = nil
-		}
-		return n, err
-	}
-	return c.Conn.Read(b)
-}
-
 func (s *Server) createClient(conn net.Conn, ws *websocket) *client {
 	// Snapshot server options.
 	opts := s.getOpts()
@@ -1897,6 +1875,12 @@ func (s *Server) createClient(conn net.Conn, ws *websocket) *client {
 	// Grab JSON info string
 	s.mu.Lock()
 	info := s.copyInfo()
+	// If this is a websocket client and there is no top-level auth specified,
+	// then we use the websocket's specific boolean that will be set to true
+	// if there is any auth{} configured in websocket{}.
+	if ws != nil && !info.AuthRequired {
+		info.AuthRequired = s.websocket.authRequired
+	}
 	if s.nonceRequired() {
 		// Nonce handling
 		var raw [nonceLen]byte
@@ -1924,6 +1908,7 @@ func (s *Server) createClient(conn net.Conn, ws *websocket) *client {
 	// TLS handshake is done (if applicable).
 	c.sendProtoNow(c.generateClientInfoJSON(info))
 
+	tlsRequired := ws == nil && info.TLSRequired
 	// Unlock to register
 	c.mu.Unlock()
 
@@ -1951,33 +1936,9 @@ func (s *Server) createClient(conn net.Conn, ws *websocket) *client {
 	// Re-Grab lock
 	c.mu.Lock()
 
-	tlsRequired := ws == nil && info.TLSRequired
-	var pre []byte
-	// If we have both TLS and non-TLS allowed we need to see which
-	// one the client wants.
-	if opts.TLSConfig != nil && opts.AllowNonTLS {
-		pre = make([]byte, 4)
-		c.nc.SetReadDeadline(time.Now().Add(secondsToDuration(opts.TLSTimeout)))
-		n, _ := io.ReadFull(c.nc, pre[:])
-		c.nc.SetReadDeadline(time.Time{})
-		pre = pre[:n]
-		if n > 0 && pre[0] == 0x16 {
-			tlsRequired = true
-		} else {
-			tlsRequired = false
-		}
-	}
-
 	// Check for TLS
 	if tlsRequired {
 		c.Debugf("Starting TLS client connection handshake")
-		// If we have a prebuffer create a multi-reader.
-		if len(pre) > 0 {
-			c.nc = &tlsMixConn{c.nc, bytes.NewBuffer(pre)}
-			// Clear pre so it is not parsed.
-			pre = nil
-		}
-
 		c.nc = tls.Server(c.nc, opts.TLSConfig)
 		conn := c.nc.(*tls.Conn)
 
@@ -2019,7 +1980,15 @@ func (s *Server) createClient(conn net.Conn, ws *websocket) *client {
 	// the race where the timer fires during the handshake and causes the
 	// server to write bad data to the socket. See issue #432.
 	if info.AuthRequired {
-		c.setAuthTimer(secondsToDuration(opts.AuthTimeout))
+		timeout := opts.AuthTimeout
+		// For websocket, possibly override only if set. We make sure that
+		// opts.AuthTimeout is set to a default value if not configured,
+		// but we don't do the same for websocket's one so that we know
+		// if user has explicitly set or not.
+		if ws != nil && opts.Websocket.AuthTimeout != 0 {
+			timeout = opts.Websocket.AuthTimeout
+		}
+		c.setAuthTimer(secondsToDuration(timeout))
 	}
 
 	// Do final client initialization
@@ -2028,7 +1997,7 @@ func (s *Server) createClient(conn net.Conn, ws *websocket) *client {
 	c.setPingTimer()
 
 	// Spin up the read loop.
-	s.startGoRoutine(func() { c.readLoop(pre) })
+	s.startGoRoutine(func() { c.readLoop() })
 
 	// Spin up the write loop.
 	s.startGoRoutine(func() { c.writeLoop() })
@@ -2761,14 +2730,6 @@ func (s *Server) lameDuckMode() {
 		s.websocket.listener = nil
 	}
 	s.ldmCh = make(chan bool, expected)
-	opts := s.getOpts()
-	gp := opts.LameDuckGracePeriod
-	// For tests, we want the grace period to be in some cases bigger
-	// than the ldm duration, so to by-pass the validateOptions() check,
-	// we use negative number and flip it here.
-	if gp < 0 {
-		gp *= -1
-	}
 	s.mu.Unlock()
 
 	// Wait for accept loops to be done to make sure that no new
@@ -2787,8 +2748,8 @@ func (s *Server) lameDuckMode() {
 		s.Shutdown()
 		return
 	}
-	dur := int64(opts.LameDuckDuration)
-	dur -= int64(gp)
+	dur := int64(s.getOpts().LameDuckDuration)
+	dur -= atomic.LoadInt64(&lameDuckModeInitialDelay)
 	if dur <= 0 {
 		dur = int64(time.Second)
 	}
@@ -2820,7 +2781,7 @@ func (s *Server) lameDuckMode() {
 	s.sendLDMToClients()
 	s.mu.Unlock()
 
-	t := time.NewTimer(gp)
+	t := time.NewTimer(time.Duration(atomic.LoadInt64(&lameDuckModeInitialDelay)))
 	// Delay start of closing of client connections in case
 	// we have several servers that we want to signal to enter LD mode
 	// and not have their client reconnect to each other.
diff --git a/server/websocket.go b/server/websocket.go
index 18a270c6..3eacbd43 100644
--- a/server/websocket.go
+++ b/server/websocket.go
@@ -101,6 +101,9 @@ type srvWebsocket struct {
 	sameOrigin     bool
 	connectURLs    []string
 	connectURLsMap map[string]struct{}
+	users          map[string]*User
+	nkeys          map[string]*NkeyUser
+	authRequired   bool // indicate if there is auth override in websocket config
 }
 
 type allowedOrigin struct {
@@ -763,6 +766,55 @@ func (s *Server) wsSetOriginOptions(o *WebsocketOpts) {
 	}
 }
 
+// Given the websocket options, we check if any auth configuration
+// has been provided. If so, possibly create users/nkey users and
+// store them in s.websocket.users/nkeys.
+// Also update a boolean that indicates if auth is required for
+// websocket clients.
+func (s *Server) wsConfigAuth(opts *WebsocketOpts) {
+	if opts.Nkeys != nil || opts.Users != nil {
+		// Support both at the same time.
+		if opts.Nkeys != nil {
+			s.websocket.nkeys = make(map[string]*NkeyUser)
+			for _, u := range opts.Nkeys {
+				copy := u.clone()
+				if u.Account != nil {
+					if v, ok := s.accounts.Load(u.Account.Name); ok {
+						copy.Account = v.(*Account)
+					}
+				}
+				if copy.Permissions != nil {
+					validateResponsePermissions(copy.Permissions)
+				}
+				s.websocket.nkeys[u.Nkey] = copy
+			}
+		}
+		if opts.Users != nil {
+			s.websocket.users = make(map[string]*User)
+			for _, u := range opts.Users {
+				copy := u.clone()
+				if u.Account != nil {
+					if v, ok := s.accounts.Load(u.Account.Name); ok {
+						copy.Account = v.(*Account)
+					}
+				}
+				if copy.Permissions != nil {
+					validateResponsePermissions(copy.Permissions)
+				}
+				s.websocket.users[u.Username] = copy
+			}
+		}
+		s.assignGlobalAccountToOrphanUsers()
+		s.websocket.authRequired = true
+	} else if opts.Username != "" || opts.Token != "" {
+		s.websocket.authRequired = true
+	} else {
+		s.websocket.users = nil
+		s.websocket.nkeys = nil
+		s.websocket.authRequired = false
+	}
+}
+
 func (s *Server) startWebsocketServer() {
 	sopts := s.getOpts()
 	o := &sopts.Websocket
@@ -787,7 +839,6 @@ func (s *Server) startWebsocketServer() {
 	if o.TLSConfig != nil {
 		proto = "wss"
 		config := o.TLSConfig.Clone()
-		config.ClientAuth = tls.NoClientCert
 		hl, err = tls.Listen("tcp", hp, config)
 	} else {
 		proto = "ws"
diff --git a/vendor/github.com/nats-io/nats.go/context.go b/vendor/github.com/nats-io/nats.go/context.go
index 769f88a0..c921d6be 100644
--- a/vendor/github.com/nats-io/nats.go/context.go
+++ b/vendor/github.com/nats-io/nats.go/context.go
@@ -11,6 +11,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// +build go1.7
+
 // A Go client for the NATS messaging system (https://nats.io).
 package nats
 
@@ -19,33 +21,9 @@ import (
 	"reflect"
 )
 
-// RequestMsgWithContext takes a context, a subject and payload
-// in bytes and request expecting a single response.
-func (nc *Conn) RequestMsgWithContext(ctx context.Context, msg *Msg) (*Msg, error) {
-	var hdr []byte
-	var err error
-
-	if len(msg.Header) > 0 {
-		if !nc.info.Headers {
-			return nil, ErrHeadersNotSupported
-		}
-
-		hdr, err = msg.headerBytes()
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return nc.requestWithContext(ctx, msg.Subject, hdr, msg.Data)
-}
-
 // RequestWithContext takes a context, a subject and payload
 // in bytes and request expecting a single response.
 func (nc *Conn) RequestWithContext(ctx context.Context, subj string, data []byte) (*Msg, error) {
-	return nc.requestWithContext(ctx, subj, nil, data)
-}
-
-func (nc *Conn) requestWithContext(ctx context.Context, subj string, hdr, data []byte) (*Msg, error) {
 	if ctx == nil {
 		return nil, ErrInvalidContext
 	}
@@ -62,10 +40,10 @@ func (nc *Conn) requestWithContext(ctx context.Context, subj string, hdr, data [
 	// If user wants the old style.
 	if nc.Opts.UseOldRequestStyle {
 		nc.mu.Unlock()
-		return nc.oldRequestWithContext(ctx, subj, hdr, data)
+		return nc.oldRequestWithContext(ctx, subj, data)
 	}
 
-	mch, token, err := nc.createNewRequestAndSend(subj, hdr, data)
+	mch, token, err := nc.createNewRequestAndSend(subj, data)
 	if err != nil {
 		return nil, err
 	}
@@ -89,7 +67,7 @@ func (nc *Conn) requestWithContext(ctx context.Context, subj string, hdr, data [
 }
 
 // oldRequestWithContext utilizes inbox and subscription per request.
-func (nc *Conn) oldRequestWithContext(ctx context.Context, subj string, hdr, data []byte) (*Msg, error) {
+func (nc *Conn) oldRequestWithContext(ctx context.Context, subj string, data []byte) (*Msg, error) {
 	inbox := NewInbox()
 	ch := make(chan *Msg, RequestChanLen)
 
@@ -100,7 +78,7 @@ func (nc *Conn) oldRequestWithContext(ctx context.Context, subj string, hdr, dat
 	s.AutoUnsubscribe(1)
 	defer s.Unsubscribe()
 
-	err = nc.publish(subj, inbox, hdr, data)
+	err = nc.PublishRequest(subj, inbox, data)
 	if err != nil {
 		return nil, err
 	}
diff --git a/vendor/github.com/nats-io/nats.go/nats.go b/vendor/github.com/nats-io/nats.go/nats.go
index daa56fdb..d3fa2b99 100644
--- a/vendor/github.com/nats-io/nats.go/nats.go
+++ b/vendor/github.com/nats-io/nats.go/nats.go
@@ -510,31 +510,6 @@ type Msg struct {
 	barrier *barrierInfo
 }
 
-func (m *Msg) headerBytes() ([]byte, error) {
-	var hdr []byte
-	if len(m.Header) == 0 {
-		return hdr, nil
-	}
-
-	var b bytes.Buffer
-	_, err := b.WriteString(hdrLine)
-	if err != nil {
-		return nil, ErrBadHeaderMsg
-	}
-
-	err = m.Header.Write(&b)
-	if err != nil {
-		return nil, ErrBadHeaderMsg
-	}
-
-	_, err = b.WriteString(crlf)
-	if err != nil {
-		return nil, ErrBadHeaderMsg
-	}
-
-	return b.Bytes(), nil
-}
-
 type barrierInfo struct {
 	refs int64
 	f    func()
@@ -568,7 +543,6 @@ type serverInfo struct {
 	Version      string   `json:"version"`
 	AuthRequired bool     `json:"auth_required"`
 	TLSRequired  bool     `json:"tls_required"`
-	TLSAvailable bool     `json:"tls_available"`
 	Headers      bool     `json:"headers"`
 	MaxPayload   int64    `json:"max_payload"`
 	ConnectURLs  []string `json:"connect_urls,omitempty"`
@@ -1602,7 +1576,7 @@ func (nc *Conn) checkForSecure() error {
 	o := nc.Opts
 
 	// Check for mismatch in setups
-	if o.Secure && !nc.info.TLSRequired && !nc.info.TLSAvailable {
+	if o.Secure && !nc.info.TLSRequired {
 		return ErrSecureConnWanted
 	} else if nc.info.TLSRequired && !o.Secure {
 		// Switch to Secure since server needs TLS.
@@ -2713,21 +2687,18 @@ func (nc *Conn) PublishMsg(m *Msg) error {
 	if m == nil {
 		return ErrInvalidMsg
 	}
-
 	var hdr []byte
-	var err error
-
 	if len(m.Header) > 0 {
 		if !nc.info.Headers {
 			return ErrHeadersNotSupported
 		}
-
-		hdr, err = m.headerBytes()
-		if err != nil {
-			return err
-		}
+		// FIXME(dlc) - Optimize
+		var b bytes.Buffer
+		b.WriteString(hdrLine)
+		m.Header.Write(&b)
+		b.WriteString(crlf)
+		hdr = b.Bytes()
 	}
-
 	return nc.publish(m.Subject, m.Reply, hdr, m.Data)
 }
 
@@ -2903,7 +2874,7 @@ func (nc *Conn) respHandler(m *Msg) {
 }
 
 // Helper to setup and send new request style requests. Return the chan to receive the response.
-func (nc *Conn) createNewRequestAndSend(subj string, hdr, data []byte) (chan *Msg, string, error) {
+func (nc *Conn) createNewRequestAndSend(subj string, data []byte) (chan *Msg, string, error) {
 	// Do setup for the new style if needed.
 	if nc.respMap == nil {
 		nc.initNewResp()
@@ -2927,55 +2898,28 @@ func (nc *Conn) createNewRequestAndSend(subj string, hdr, data []byte) (chan *Ms
 	}
 	nc.mu.Unlock()
 
-	if err := nc.publish(subj, respInbox, hdr, data); err != nil {
+	if err := nc.PublishRequest(subj, respInbox, data); err != nil {
 		return nil, token, err
 	}
 
 	return mch, token, nil
 }
 
-// RequestMsg will send a request payload including optional headers and deliver
-// the response message, or an error, including a timeout if no message was received properly.
-func (nc *Conn) RequestMsg(msg *Msg, timeout time.Duration) (*Msg, error) {
-	var hdr []byte
-	var err error
-
-	if len(msg.Header) > 0 {
-		if !nc.info.Headers {
-			return nil, ErrHeadersNotSupported
-		}
-
-		hdr, err = msg.headerBytes()
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return nc.request(msg.Subject, hdr, msg.Data, timeout)
-}
-
 // Request will send a request payload and deliver the response message,
 // or an error, including a timeout if no message was received properly.
 func (nc *Conn) Request(subj string, data []byte, timeout time.Duration) (*Msg, error) {
-	return nc.request(subj, nil, data, timeout)
-}
-
-func (nc *Conn) request(subj string, hdr, data []byte, timeout time.Duration) (*Msg, error) {
 	if nc == nil {
 		return nil, ErrInvalidConnection
 	}
 
 	nc.mu.Lock()
+	// If user wants the old style.
 	if nc.Opts.UseOldRequestStyle {
 		nc.mu.Unlock()
-		return nc.oldRequest(subj, hdr, data, timeout)
+		return nc.oldRequest(subj, data, timeout)
 	}
 
-	return nc.newRequest(subj, hdr, data, timeout)
-}
-
-func (nc *Conn) newRequest(subj string, hdr, data []byte, timeout time.Duration) (*Msg, error) {
-	mch, token, err := nc.createNewRequestAndSend(subj, hdr, data)
+	mch, token, err := nc.createNewRequestAndSend(subj, data)
 	if err != nil {
 		return nil, err
 	}
@@ -3004,7 +2948,7 @@ func (nc *Conn) newRequest(subj string, hdr, data []byte, timeout time.Duration)
 // oldRequest will create an Inbox and perform a Request() call
 // with the Inbox reply and return the first reply received.
 // This is optimized for the case of multiple responses.
-func (nc *Conn) oldRequest(subj string, hdr, data []byte, timeout time.Duration) (*Msg, error) {
+func (nc *Conn) oldRequest(subj string, data []byte, timeout time.Duration) (*Msg, error) {
 	inbox := NewInbox()
 	ch := make(chan *Msg, RequestChanLen)
 
@@ -3015,11 +2959,10 @@ func (nc *Conn) oldRequest(subj string, hdr, data []byte, timeout time.Duration)
 	s.AutoUnsubscribe(1)
 	defer s.Unsubscribe()
 
-	err = nc.publish(subj, inbox, hdr, data)
+	err = nc.PublishRequest(subj, inbox, data)
 	if err != nil {
 		return nil, err
 	}
-
 	return s.NextMsg(timeout)
 }
 
@@ -3710,21 +3653,6 @@ func (m *Msg) Respond(data []byte) error {
 	return nc.Publish(m.Reply, data)
 }
 
-// RespondMsg allows a convenient way to respond to requests in service based subscriptions that might include headers
-func (m *Msg) RespondMsg(msg *Msg) error {
-	if m == nil || m.Sub == nil {
-		return ErrMsgNotBound
-	}
-	if m.Reply == "" {
-		return ErrMsgNoReply
-	}
-	m.Sub.mu.Lock()
-	nc := m.Sub.conn
-	m.Sub.mu.Unlock()
-	// No need to check the connection here since the call to publish will do all the checking.
-	return nc.PublishMsg(msg)
-}
-
 // FIXME: This is a hack
 // removeFlushEntry is needed when we need to discard queued up responses
 // for our pings as part of a flush call. This happens when we have a flush
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 6a05436b..f99068cb 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -2,7 +2,7 @@
 github.com/minio/highwayhash
 # github.com/nats-io/jwt/v2 v2.0.0-20200602193336-473d698956ed
 github.com/nats-io/jwt/v2
-# github.com/nats-io/nats.go v1.10.1-0.20200606002146-fc6fed82929a
+# github.com/nats-io/nats.go v1.10.1-0.20200601214746-e93e18d0ed6f
 github.com/nats-io/nats.go
 github.com/nats-io/nats.go/encoders/builtin
 github.com/nats-io/nats.go/util


Test patch:
diff --git a/server/config_check_test.go b/server/config_check_test.go
index 63ef54ef..0485c613 100644
--- a/server/config_check_test.go
+++ b/server/config_check_test.go
@@ -1090,38 +1090,11 @@ func TestConfigCheck(t *testing.T) {
 		{
 			name: "invalid lame_duck_duration type",
 			config: `
-				lame_duck_duration: abc
+		lame_duck_duration: abc
 			`,
 			err:       errors.New(`error parsing lame_duck_duration: time: invalid duration abc`),
 			errorLine: 2,
-			errorPos:  5,
-		},
-		{
-			name: "lame_duck_duration too small",
-			config: `
-				lame_duck_duration: "5s"
-			`,
-			err:       errors.New(`invalid lame_duck_duration of 5s, minimum is 30 seconds`),
-			errorLine: 2,
-			errorPos:  5,
-		},
-		{
-			name: "invalid lame_duck_grace_period type",
-			config: `
-				lame_duck_grace_period: abc
-			`,
-			err:       errors.New(`error parsing lame_duck_grace_period: time: invalid duration abc`),
-			errorLine: 2,
-			errorPos:  5,
-		},
-		{
-			name: "lame_duck_grace_period should be positive",
-			config: `
-				lame_duck_grace_period: "-5s"
-			`,
-			err:       errors.New(`invalid lame_duck_grace_period, needs to be positive`),
-			errorLine: 2,
-			errorPos:  5,
+			errorPos:  3,
 		},
 		{
 			name: "when only setting TLS timeout for a leafnode remote",
diff --git a/server/opts_test.go b/server/opts_test.go
index d257b145..00140da7 100644
--- a/server/opts_test.go
+++ b/server/opts_test.go
@@ -45,21 +45,20 @@ func checkOptionsEqual(t *testing.T, golden, opts *Options) {
 
 func TestDefaultOptions(t *testing.T) {
 	golden := &Options{
-		Host:                DEFAULT_HOST,
-		Port:                DEFAULT_PORT,
-		MaxConn:             DEFAULT_MAX_CONNECTIONS,
-		HTTPHost:            DEFAULT_HOST,
-		PingInterval:        DEFAULT_PING_INTERVAL,
-		MaxPingsOut:         DEFAULT_PING_MAX_OUT,
-		TLSTimeout:          float64(TLS_TIMEOUT) / float64(time.Second),
-		AuthTimeout:         float64(AUTH_TIMEOUT) / float64(time.Second),
-		MaxControlLine:      MAX_CONTROL_LINE_SIZE,
-		MaxPayload:          MAX_PAYLOAD_SIZE,
-		MaxPending:          MAX_PENDING_SIZE,
-		WriteDeadline:       DEFAULT_FLUSH_DEADLINE,
-		MaxClosedClients:    DEFAULT_MAX_CLOSED_CLIENTS,
-		LameDuckDuration:    DEFAULT_LAME_DUCK_DURATION,
-		LameDuckGracePeriod: DEFAULT_LAME_DUCK_GRACE_PERIOD,
+		Host:             DEFAULT_HOST,
+		Port:             DEFAULT_PORT,
+		MaxConn:          DEFAULT_MAX_CONNECTIONS,
+		HTTPHost:         DEFAULT_HOST,
+		PingInterval:     DEFAULT_PING_INTERVAL,
+		MaxPingsOut:      DEFAULT_PING_MAX_OUT,
+		TLSTimeout:       float64(TLS_TIMEOUT) / float64(time.Second),
+		AuthTimeout:      float64(AUTH_TIMEOUT) / float64(time.Second),
+		MaxControlLine:   MAX_CONTROL_LINE_SIZE,
+		MaxPayload:       MAX_PAYLOAD_SIZE,
+		MaxPending:       MAX_PENDING_SIZE,
+		WriteDeadline:    DEFAULT_FLUSH_DEADLINE,
+		MaxClosedClients: DEFAULT_MAX_CLOSED_CLIENTS,
+		LameDuckDuration: DEFAULT_LAME_DUCK_DURATION,
 		LeafNode: LeafNodeOpts{
 			ReconnectInterval: DEFAULT_LEAF_NODE_RECONNECT,
 		},
diff --git a/server/server_test.go b/server/server_test.go
index 97f6ee08..c3e805a1 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -665,30 +665,11 @@ func TestProfilingNoTimeout(t *testing.T) {
 	}
 }
 
-func TestLameDuckOptionsValidation(t *testing.T) {
-	o := DefaultOptions()
-	o.LameDuckDuration = 5 * time.Second
-	o.LameDuckGracePeriod = 10 * time.Second
-	s, err := NewServer(o)
-	if s != nil {
-		s.Shutdown()
-	}
-	if err == nil || !strings.Contains(err.Error(), "should be strictly lower") {
-		t.Fatalf("Expected error saying that ldm grace period should be lower than ldm duration, got %v", err)
-	}
-}
-
-func testSetLDMGracePeriod(o *Options, val time.Duration) {
-	// For tests, we set the grace period as a negative value
-	// so we can have a grace period bigger than the total duration.
-	// When validating options, we would not be able to run the
-	// server without this trick.
-	o.LameDuckGracePeriod = val * -1
-}
-
 func TestLameDuckMode(t *testing.T) {
+	atomic.StoreInt64(&lameDuckModeInitialDelay, 0)
+	defer atomic.StoreInt64(&lameDuckModeInitialDelay, lameDuckModeDefaultInitialDelay)
+
 	optsA := DefaultOptions()
-	testSetLDMGracePeriod(optsA, time.Nanosecond)
 	optsA.Cluster.Host = "127.0.0.1"
 	srvA := RunServer(optsA)
 	defer srvA.Shutdown()
@@ -849,21 +830,19 @@ func TestLameDuckMode(t *testing.T) {
 	// Now test that we introduce delay before starting closing client connections.
 	// This allow to "signal" multiple servers and avoid their clients to reconnect
 	// to a server that is going to be going in LD mode.
-	testSetLDMGracePeriod(optsA, 100*time.Millisecond)
+	atomic.StoreInt64(&lameDuckModeInitialDelay, int64(100*time.Millisecond))
+
 	optsA.LameDuckDuration = 10 * time.Millisecond
 	srvA = RunServer(optsA)
 	defer srvA.Shutdown()
 
 	optsB.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", srvA.ClusterAddr().Port))
-	testSetLDMGracePeriod(optsB, 100*time.Millisecond)
 	optsB.LameDuckDuration = 10 * time.Millisecond
 	srvB = RunServer(optsB)
 	defer srvB.Shutdown()
 
 	optsC := DefaultOptions()
 	optsC.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", srvA.ClusterAddr().Port))
-	testSetLDMGracePeriod(optsC, 100*time.Millisecond)
-	optsC.LameDuckGracePeriod = -100 * time.Millisecond
 	optsC.LameDuckDuration = 10 * time.Millisecond
 	srvC := RunServer(optsC)
 	defer srvC.Shutdown()
@@ -899,13 +878,15 @@ func TestLameDuckMode(t *testing.T) {
 }
 
 func TestLameDuckModeInfo(t *testing.T) {
-	optsA := testWSOptions()
-	optsA.Cluster.Host = "127.0.0.1"
-	optsA.Cluster.Port = -1
 	// Ensure that initial delay is set very high so that we can
 	// check that some events occur as expected before the client
 	// is disconnected.
-	testSetLDMGracePeriod(optsA, 5*time.Second)
+	atomic.StoreInt64(&lameDuckModeInitialDelay, int64(5*time.Second))
+	defer atomic.StoreInt64(&lameDuckModeInitialDelay, lameDuckModeDefaultInitialDelay)
+
+	optsA := testWSOptions()
+	optsA.Cluster.Host = "127.0.0.1"
+	optsA.Cluster.Port = -1
 	optsA.LameDuckDuration = 50 * time.Millisecond
 	optsA.DisableShortFirstPing = true
 	srvA := RunServer(optsA)
@@ -973,7 +954,6 @@ func TestLameDuckModeInfo(t *testing.T) {
 	checkConnectURLs(expected)
 
 	optsC := testWSOptions()
-	testSetLDMGracePeriod(optsA, 5*time.Second)
 	optsC.Routes = RoutesFromStr(fmt.Sprintf("nats://127.0.0.1:%d", srvA.ClusterAddr().Port))
 	srvC := RunServer(optsC)
 	defer srvC.Shutdown()
diff --git a/server/websocket_test.go b/server/websocket_test.go
index 1f81fe67..7e646bed 100644
--- a/server/websocket_test.go
+++ b/server/websocket_test.go
@@ -18,6 +18,7 @@ import (
 	"bytes"
 	"compress/flate"
 	"crypto/tls"
+	"encoding/base64"
 	"encoding/binary"
 	"encoding/json"
 	"errors"
@@ -34,6 +35,8 @@ import (
 	"sync"
 	"testing"
 	"time"
+
+	"github.com/nats-io/nkeys"
 )
 
 type testReader struct {
@@ -1581,7 +1584,7 @@ func TestWSAbnormalFailureOfWebServer(t *testing.T) {
 	}
 }
 
-func testWSCreateClient(t testing.TB, compress, web bool, host string, port int) (net.Conn, *bufio.Reader) {
+func testWSCreateClientGetInfo(t testing.TB, compress, web bool, host string, port int) (net.Conn, *bufio.Reader, []byte) {
 	t.Helper()
 	addr := fmt.Sprintf("%s:%d", host, port)
 	wsc, err := net.Dial("tcp", addr)
@@ -1613,9 +1616,15 @@ func testWSCreateClient(t testing.TB, compress, web bool, host string, port int)
 		t.Fatalf("Expected response status %v, got %v", http.StatusSwitchingProtocols, resp.StatusCode)
 	}
 	// Wait for the INFO
-	if msg := testWSReadFrame(t, br); !bytes.HasPrefix(msg, []byte("INFO {")) {
-		t.Fatalf("Expected INFO, got %s", msg)
+	info := testWSReadFrame(t, br)
+	if !bytes.HasPrefix(info, []byte("INFO {")) {
+		t.Fatalf("Expected INFO, got %s", info)
 	}
+	return wsc, br, info
+}
+
+func testWSCreateClient(t testing.TB, compress, web bool, host string, port int) (net.Conn, *bufio.Reader) {
+	wsc, br, _ := testWSCreateClientGetInfo(t, compress, web, host, port)
 	// Send CONNECT and PING
 	wsmsg := testWSCreateClientMsg(wsBinaryMessage, 1, true, compress, []byte("CONNECT {\"verbose\":false,\"protocol\":1}\r\nPING\r\n"))
 	if _, err := wsc.Write(wsmsg); err != nil {
@@ -1792,6 +1801,187 @@ func TestWSTLSConnection(t *testing.T) {
 	}
 }
 
+func TestWSTLSVerifyClientCert(t *testing.T) {
+	o := testWSOptions()
+	tc := &TLSConfigOpts{
+		CertFile: "../test/configs/certs/server-cert.pem",
+		KeyFile:  "../test/configs/certs/server-key.pem",
+		CaFile:   "../test/configs/certs/ca.pem",
+		Verify:   true,
+	}
+	tlsc, err := GenTLSConfig(tc)
+	if err != nil {
+		t.Fatalf("Error creating tls config: %v", err)
+	}
+	o.Websocket.TLSConfig = tlsc
+	s := RunServer(o)
+	defer s.Shutdown()
+
+	addr := fmt.Sprintf("%s:%d", o.Websocket.Host, o.Websocket.Port)
+
+	for _, test := range []struct {
+		name        string
+		provideCert bool
+	}{
+		{"client provides cert", true},
+		{"client does not provide cert", false},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			wsc, err := net.Dial("tcp", addr)
+			if err != nil {
+				t.Fatalf("Error creating ws connection: %v", err)
+			}
+			defer wsc.Close()
+			tlsc := &tls.Config{}
+			if test.provideCert {
+				tc := &TLSConfigOpts{
+					CertFile: "../test/configs/certs/client-cert.pem",
+					KeyFile:  "../test/configs/certs/client-key.pem",
+				}
+				var err error
+				tlsc, err = GenTLSConfig(tc)
+				if err != nil {
+					t.Fatalf("Error generating tls config: %v", err)
+				}
+			}
+			tlsc.InsecureSkipVerify = true
+			wsc = tls.Client(wsc, tlsc)
+			if err := wsc.(*tls.Conn).Handshake(); err != nil {
+				t.Fatalf("Error during handshake: %v", err)
+			}
+			req := testWSCreateValidReq()
+			req.URL, _ = url.Parse("wss://" + addr)
+			if err := req.Write(wsc); err != nil {
+				t.Fatalf("Error sending request: %v", err)
+			}
+			br := bufio.NewReader(wsc)
+			resp, err := http.ReadResponse(br, req)
+			if resp != nil {
+				resp.Body.Close()
+			}
+			if !test.provideCert {
+				if err == nil {
+					t.Fatal("Expected error, did not get one")
+				} else if !strings.Contains(err.Error(), "bad certificate") {
+					t.Fatalf("Unexpected error: %v", err)
+				}
+				return
+			}
+			if err != nil {
+				t.Fatalf("Unexpected error: %v", err)
+			}
+			if resp.StatusCode != http.StatusSwitchingProtocols {
+				t.Fatalf("Expected status %v, got %v", http.StatusSwitchingProtocols, resp.StatusCode)
+			}
+		})
+	}
+}
+
+func TestWSTLSVerifyAndMap(t *testing.T) {
+	o := testWSOptions()
+	certUserName := "CN=example.com,OU=NATS.io"
+	o.Users = []*User{&User{Username: certUserName}}
+	tc := &TLSConfigOpts{
+		CertFile: "../test/configs/certs/tlsauth/server.pem",
+		KeyFile:  "../test/configs/certs/tlsauth/server-key.pem",
+		CaFile:   "../test/configs/certs/tlsauth/ca.pem",
+		Verify:   true,
+	}
+	tlsc, err := GenTLSConfig(tc)
+	if err != nil {
+		t.Fatalf("Error creating tls config: %v", err)
+	}
+	o.Websocket.TLSConfig = tlsc
+	o.Websocket.TLSMap = true
+	s := RunServer(o)
+	defer s.Shutdown()
+
+	addr := fmt.Sprintf("%s:%d", o.Websocket.Host, o.Websocket.Port)
+
+	for _, test := range []struct {
+		name        string
+		provideCert bool
+	}{
+		{"client provides cert", true},
+		{"client does not provide cert", false},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			wsc, err := net.Dial("tcp", addr)
+			if err != nil {
+				t.Fatalf("Error creating ws connection: %v", err)
+			}
+			defer wsc.Close()
+			tlsc := &tls.Config{}
+			if test.provideCert {
+				tc := &TLSConfigOpts{
+					CertFile: "../test/configs/certs/tlsauth/client.pem",
+					KeyFile:  "../test/configs/certs/tlsauth/client-key.pem",
+				}
+				var err error
+				tlsc, err = GenTLSConfig(tc)
+				if err != nil {
+					t.Fatalf("Error generating tls config: %v", err)
+				}
+			}
+			tlsc.InsecureSkipVerify = true
+			wsc = tls.Client(wsc, tlsc)
+			if err := wsc.(*tls.Conn).Handshake(); err != nil {
+				t.Fatalf("Error during handshake: %v", err)
+			}
+			req := testWSCreateValidReq()
+			req.URL, _ = url.Parse("wss://" + addr)
+			if err := req.Write(wsc); err != nil {
+				t.Fatalf("Error sending request: %v", err)
+			}
+			br := bufio.NewReader(wsc)
+			resp, err := http.ReadResponse(br, req)
+			if resp != nil {
+				resp.Body.Close()
+			}
+			if !test.provideCert {
+				if err == nil {
+					t.Fatal("Expected error, did not get one")
+				} else if !strings.Contains(err.Error(), "bad certificate") {
+					t.Fatalf("Unexpected error: %v", err)
+				}
+				return
+			}
+			if err != nil {
+				t.Fatalf("Unexpected error: %v", err)
+			}
+			if resp.StatusCode != http.StatusSwitchingProtocols {
+				t.Fatalf("Expected status %v, got %v", http.StatusSwitchingProtocols, resp.StatusCode)
+			}
+			// Wait for the INFO
+			l := testWSReadFrame(t, br)
+			if !bytes.HasPrefix(l, []byte("INFO {")) {
+				t.Fatalf("Expected INFO, got %s", l)
+			}
+			var info serverInfo
+			if err := json.Unmarshal(l[5:], &info); err != nil {
+				t.Fatalf("Unable to unmarshal info: %v", err)
+			}
+			// Send CONNECT and PING
+			wsmsg := testWSCreateClientMsg(wsBinaryMessage, 1, true, false, []byte("CONNECT {\"verbose\":false,\"protocol\":1}\r\nPING\r\n"))
+			if _, err := wsc.Write(wsmsg); err != nil {
+				t.Fatalf("Error sending message: %v", err)
+			}
+			// Wait for the PONG
+			if msg := testWSReadFrame(t, br); !bytes.HasPrefix(msg, []byte("PONG\r\n")) {
+				t.Fatalf("Expected PONG, got %s", msg)
+			}
+
+			c := s.getClient(info.CID)
+			c.mu.Lock()
+			un := c.opts.Username
+			c.mu.Unlock()
+			if un != certUserName {
+				t.Fatalf("Expected client's assigned username to be %q, got %q", certUserName, un)
+			}
+		})
+	}
+}
+
 func TestWSHandshakeTimeout(t *testing.T) {
 	o := testWSOptions()
 	o.Websocket.HandshakeTimeout = time.Millisecond
@@ -2406,6 +2596,521 @@ func TestWSCompressionFrameSizeLimit(t *testing.T) {
 	}
 }
 
+func TestWSBasicAuth(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		opts func() *Options
+		user string
+		pass string
+		err  string
+	}{
+		{
+			"top level auth, no override, wrong u/p",
+			func() *Options {
+				o := testWSOptions()
+				o.Username = "normal"
+				o.Password = "client"
+				return o
+			},
+			"websocket", "client", "-ERR 'Authorization Violation'",
+		},
+		{
+			"top level auth, no override, correct u/p",
+			func() *Options {
+				o := testWSOptions()
+				o.Username = "normal"
+				o.Password = "client"
+				return o
+			},
+			"normal", "client", "",
+		},
+		{
+			"no top level auth, ws auth, wrong u/p",
+			func() *Options {
+				o := testWSOptions()
+				o.Websocket.Username = "websocket"
+				o.Websocket.Password = "client"
+				return o
+			},
+			"normal", "client", "-ERR 'Authorization Violation'",
+		},
+		{
+			"no top level auth, ws auth, correct u/p",
+			func() *Options {
+				o := testWSOptions()
+				o.Websocket.Username = "websocket"
+				o.Websocket.Password = "client"
+				return o
+			},
+			"websocket", "client", "",
+		},
+		{
+			"top level auth, ws override, wrong u/p",
+			func() *Options {
+				o := testWSOptions()
+				o.Username = "normal"
+				o.Password = "client"
+				o.Websocket.Username = "websocket"
+				o.Websocket.Password = "client"
+				return o
+			},
+			"normal", "client", "-ERR 'Authorization Violation'",
+		},
+		{
+			"top level auth, ws override, correct u/p",
+			func() *Options {
+				o := testWSOptions()
+				o.Username = "normal"
+				o.Password = "client"
+				o.Websocket.Username = "websocket"
+				o.Websocket.Password = "client"
+				return o
+			},
+			"websocket", "client", "",
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			o := test.opts()
+			s := RunServer(o)
+			defer s.Shutdown()
+
+			wsc, br, _ := testWSCreateClientGetInfo(t, false, false, o.Websocket.Host, o.Websocket.Port)
+			defer wsc.Close()
+
+			connectProto := fmt.Sprintf("CONNECT {\"verbose\":false,\"protocol\":1,\"user\":\"%s\",\"pass\":\"%s\"}\r\nPING\r\n",
+				test.user, test.pass)
+
+			wsmsg := testWSCreateClientMsg(wsBinaryMessage, 1, true, false, []byte(connectProto))
+			if _, err := wsc.Write(wsmsg); err != nil {
+				t.Fatalf("Error sending message: %v", err)
+			}
+			msg := testWSReadFrame(t, br)
+			if test.err == "" && !bytes.HasPrefix(msg, []byte("PONG\r\n")) {
+				t.Fatalf("Expected to receive PONG, got %q", msg)
+			} else if test.err != "" && !bytes.HasPrefix(msg, []byte(test.err)) {
+				t.Fatalf("Expected to receive %q, got %q", test.err, msg)
+			}
+		})
+	}
+}
+
+func TestWSAuthTimeout(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		at   float64
+		wat  float64
+		err  string
+	}{
+		{"use top-level auth timeout", 10.0, 0.0, ""},
+		{"use websocket auth timeout", 10.0, 0.05, "-ERR 'Authentication Timeout'"},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			o := testWSOptions()
+			o.AuthTimeout = test.at
+			o.Websocket.Username = "websocket"
+			o.Websocket.Password = "client"
+			o.Websocket.AuthTimeout = test.wat
+			s := RunServer(o)
+			defer s.Shutdown()
+
+			wsc, br, l := testWSCreateClientGetInfo(t, false, false, o.Websocket.Host, o.Websocket.Port)
+			defer wsc.Close()
+
+			var info serverInfo
+			json.Unmarshal([]byte(l[5:]), &info)
+			// Make sure that we are told that auth is required.
+			if !info.AuthRequired {
+				t.Fatalf("Expected auth required, was not: %q", l)
+			}
+			start := time.Now()
+			// Wait before sending connect
+			time.Sleep(100 * time.Millisecond)
+			connectProto := "CONNECT {\"verbose\":false,\"protocol\":1,\"user\":\"websocket\",\"pass\":\"client\"}\r\nPING\r\n"
+			wsmsg := testWSCreateClientMsg(wsBinaryMessage, 1, true, false, []byte(connectProto))
+			if _, err := wsc.Write(wsmsg); err != nil {
+				t.Fatalf("Error sending message: %v", err)
+			}
+			msg := testWSReadFrame(t, br)
+			if test.err != "" && !bytes.HasPrefix(msg, []byte(test.err)) {
+				t.Fatalf("Expected to receive %q error, got %q", test.err, msg)
+			} else if test.err == "" && !bytes.HasPrefix(msg, []byte("PONG\r\n")) {
+				t.Fatalf("Unexpected error: %q", msg)
+			}
+			if dur := time.Since(start); dur > time.Second {
+				t.Fatalf("Too long to get timeout error: %v", dur)
+			}
+		})
+	}
+}
+
+func TestWSTokenAuth(t *testing.T) {
+	for _, test := range []struct {
+		name  string
+		opts  func() *Options
+		token string
+		err   string
+	}{
+		{
+			"top level auth, no override, wrong token",
+			func() *Options {
+				o := testWSOptions()
+				o.Authorization = "goodtoken"
+				return o
+			},
+			"badtoken", "-ERR 'Authorization Violation'",
+		},
+		{
+			"top level auth, no override, correct token",
+			func() *Options {
+				o := testWSOptions()
+				o.Authorization = "goodtoken"
+				return o
+			},
+			"goodtoken", "",
+		},
+		{
+			"no top level auth, ws auth, wrong token",
+			func() *Options {
+				o := testWSOptions()
+				o.Websocket.Token = "goodtoken"
+				return o
+			},
+			"badtoken", "-ERR 'Authorization Violation'",
+		},
+		{
+			"no top level auth, ws auth, correct token",
+			func() *Options {
+				o := testWSOptions()
+				o.Websocket.Token = "goodtoken"
+				return o
+			},
+			"goodtoken", "",
+		},
+		{
+			"top level auth, ws override, wrong token",
+			func() *Options {
+				o := testWSOptions()
+				o.Authorization = "clienttoken"
+				o.Websocket.Token = "websockettoken"
+				return o
+			},
+			"clienttoken", "-ERR 'Authorization Violation'",
+		},
+		{
+			"top level auth, ws override, correct token",
+			func() *Options {
+				o := testWSOptions()
+				o.Authorization = "clienttoken"
+				o.Websocket.Token = "websockettoken"
+				return o
+			},
+			"websockettoken", "",
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			o := test.opts()
+			s := RunServer(o)
+			defer s.Shutdown()
+
+			wsc, br, _ := testWSCreateClientGetInfo(t, false, false, o.Websocket.Host, o.Websocket.Port)
+			defer wsc.Close()
+
+			connectProto := fmt.Sprintf("CONNECT {\"verbose\":false,\"protocol\":1,\"auth_token\":\"%s\"}\r\nPING\r\n",
+				test.token)
+
+			wsmsg := testWSCreateClientMsg(wsBinaryMessage, 1, true, false, []byte(connectProto))
+			if _, err := wsc.Write(wsmsg); err != nil {
+				t.Fatalf("Error sending message: %v", err)
+			}
+			msg := testWSReadFrame(t, br)
+			if test.err == "" && !bytes.HasPrefix(msg, []byte("PONG\r\n")) {
+				t.Fatalf("Expected to receive PONG, got %q", msg)
+			} else if test.err != "" && !bytes.HasPrefix(msg, []byte(test.err)) {
+				t.Fatalf("Expected to receive %q, got %q", test.err, msg)
+			}
+		})
+	}
+}
+
+func TestWSUsersAuth(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		opts func() *Options
+		user string
+		pass string
+		err  string
+	}{
+		{
+			"top level auth, no override, wrong user",
+			func() *Options {
+				o := testWSOptions()
+				o.Users = []*User{
+					&User{Username: "normal1", Password: "client1"},
+					&User{Username: "normal2", Password: "client2"},
+				}
+				return o
+			},
+			"websocket", "client", "-ERR 'Authorization Violation'",
+		},
+		{
+			"top level auth, no override, correct user",
+			func() *Options {
+				o := testWSOptions()
+				o.Users = []*User{
+					&User{Username: "normal1", Password: "client1"},
+					&User{Username: "normal2", Password: "client2"},
+				}
+				return o
+			},
+			"normal2", "client2", "",
+		},
+		{
+			"no top level auth, ws auth, wrong user",
+			func() *Options {
+				o := testWSOptions()
+				o.Websocket.Users = []*User{
+					&User{Username: "websocket1", Password: "client1"},
+					&User{Username: "websocket2", Password: "client2"},
+				}
+				return o
+			},
+			"websocket", "client", "-ERR 'Authorization Violation'",
+		},
+		{
+			"no top level auth, ws auth, correct token",
+			func() *Options {
+				o := testWSOptions()
+				o.Websocket.Users = []*User{
+					&User{Username: "websocket1", Password: "client1"},
+					&User{Username: "websocket2", Password: "client2"},
+				}
+				return o
+			},
+			"websocket1", "client1", "",
+		},
+		{
+			"top level auth, ws override, wrong user",
+			func() *Options {
+				o := testWSOptions()
+				o.Users = []*User{
+					&User{Username: "normal1", Password: "client1"},
+					&User{Username: "normal2", Password: "client2"},
+				}
+				o.Websocket.Users = []*User{
+					&User{Username: "websocket1", Password: "client1"},
+					&User{Username: "websocket2", Password: "client2"},
+				}
+				return o
+			},
+			"normal2", "client2", "-ERR 'Authorization Violation'",
+		},
+		{
+			"top level auth, ws override, correct token",
+			func() *Options {
+				o := testWSOptions()
+				o.Users = []*User{
+					&User{Username: "normal1", Password: "client1"},
+					&User{Username: "normal2", Password: "client2"},
+				}
+				o.Websocket.Users = []*User{
+					&User{Username: "websocket1", Password: "client1"},
+					&User{Username: "websocket2", Password: "client2"},
+				}
+				return o
+			},
+			"websocket2", "client2", "",
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			o := test.opts()
+			s := RunServer(o)
+			defer s.Shutdown()
+
+			wsc, br, _ := testWSCreateClientGetInfo(t, false, false, o.Websocket.Host, o.Websocket.Port)
+			defer wsc.Close()
+
+			connectProto := fmt.Sprintf("CONNECT {\"verbose\":false,\"protocol\":1,\"user\":\"%s\",\"pass\":\"%s\"}\r\nPING\r\n",
+				test.user, test.pass)
+
+			wsmsg := testWSCreateClientMsg(wsBinaryMessage, 1, true, false, []byte(connectProto))
+			if _, err := wsc.Write(wsmsg); err != nil {
+				t.Fatalf("Error sending message: %v", err)
+			}
+			msg := testWSReadFrame(t, br)
+			if test.err == "" && !bytes.HasPrefix(msg, []byte("PONG\r\n")) {
+				t.Fatalf("Expected to receive PONG, got %q", msg)
+			} else if test.err != "" && !bytes.HasPrefix(msg, []byte(test.err)) {
+				t.Fatalf("Expected to receive %q, got %q", test.err, msg)
+			}
+		})
+	}
+}
+
+func TestWSNoAuthUser(t *testing.T) {
+	for _, test := range []struct {
+		name          string
+		noAuthUser    string
+		wsNoAuthUser  string
+		user          string
+		acc           string
+		createWSUsers bool
+	}{
+		{"use top-level no_auth_user", "user1", "", "user1", "normal", false},
+		{"use websocket no_auth_user no ws users", "user1", "user2", "user2", "normal", false},
+		{"use websocket no_auth_user with ws users", "user1", "wsuser1", "wsuser1", "websocket", true},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			o := testWSOptions()
+			normalAcc := NewAccount("normal")
+			websocketAcc := NewAccount("websocket")
+			o.Accounts = []*Account{normalAcc, websocketAcc}
+			o.Users = []*User{
+				&User{Username: "user1", Password: "pwd1", Account: normalAcc},
+				&User{Username: "user2", Password: "pwd2", Account: normalAcc},
+			}
+			o.NoAuthUser = test.noAuthUser
+			o.Websocket.NoAuthUser = test.wsNoAuthUser
+			if test.createWSUsers {
+				o.Websocket.Users = []*User{
+					&User{Username: "wsuser1", Password: "pwd1", Account: websocketAcc},
+					&User{Username: "wsuser2", Password: "pwd2", Account: websocketAcc},
+				}
+			}
+			s := RunServer(o)
+			defer s.Shutdown()
+
+			wsc, br, l := testWSCreateClientGetInfo(t, false, false, o.Websocket.Host, o.Websocket.Port)
+			defer wsc.Close()
+
+			var info serverInfo
+			json.Unmarshal([]byte(l[5:]), &info)
+
+			connectProto := "CONNECT {\"verbose\":false,\"protocol\":1}\r\nPING\r\n"
+			wsmsg := testWSCreateClientMsg(wsBinaryMessage, 1, true, false, []byte(connectProto))
+			if _, err := wsc.Write(wsmsg); err != nil {
+				t.Fatalf("Error sending message: %v", err)
+			}
+			msg := testWSReadFrame(t, br)
+			if !bytes.HasPrefix(msg, []byte("PONG\r\n")) {
+				t.Fatalf("Unexpected error: %q", msg)
+			}
+
+			c := s.getClient(info.CID)
+			c.mu.Lock()
+			uname := c.opts.Username
+			aname := c.acc.GetName()
+			c.mu.Unlock()
+			if uname != test.user {
+				t.Fatalf("Expected selected user to be %q, got %q", test.user, uname)
+			}
+			if aname != test.acc {
+				t.Fatalf("Expected selected account to be %q, got %q", test.acc, aname)
+			}
+		})
+	}
+}
+
+func TestWSNkeyAuth(t *testing.T) {
+	nkp, _ := nkeys.CreateUser()
+	pub, _ := nkp.PublicKey()
+
+	wsnkp, _ := nkeys.CreateUser()
+	wspub, _ := wsnkp.PublicKey()
+
+	for _, test := range []struct {
+		name string
+		opts func() *Options
+		nkey string
+		kp   nkeys.KeyPair
+		err  string
+	}{
+		{
+			"top level auth, no override, wrong nkey",
+			func() *Options {
+				o := testWSOptions()
+				o.Nkeys = []*NkeyUser{&NkeyUser{Nkey: pub}}
+				return o
+			},
+			wspub, wsnkp, "-ERR 'Authorization Violation'",
+		},
+		{
+			"top level auth, no override, correct nkey",
+			func() *Options {
+				o := testWSOptions()
+				o.Nkeys = []*NkeyUser{&NkeyUser{Nkey: pub}}
+				return o
+			},
+			pub, nkp, "",
+		},
+		{
+			"no top level auth, ws auth, wrong nkey",
+			func() *Options {
+				o := testWSOptions()
+				o.Websocket.Nkeys = []*NkeyUser{&NkeyUser{Nkey: wspub}}
+				return o
+			},
+			pub, nkp, "-ERR 'Authorization Violation'",
+		},
+		{
+			"no top level auth, ws auth, correct nkey",
+			func() *Options {
+				o := testWSOptions()
+				o.Websocket.Nkeys = []*NkeyUser{&NkeyUser{Nkey: wspub}}
+				return o
+			},
+			wspub, wsnkp, "",
+		},
+		{
+			"top level auth, ws override, wrong nkey",
+			func() *Options {
+				o := testWSOptions()
+				o.Nkeys = []*NkeyUser{&NkeyUser{Nkey: pub}}
+				o.Websocket.Nkeys = []*NkeyUser{&NkeyUser{Nkey: wspub}}
+				return o
+			},
+			pub, nkp, "-ERR 'Authorization Violation'",
+		},
+		{
+			"top level auth, ws override, correct nkey",
+			func() *Options {
+				o := testWSOptions()
+				o.Nkeys = []*NkeyUser{&NkeyUser{Nkey: pub}}
+				o.Websocket.Nkeys = []*NkeyUser{&NkeyUser{Nkey: wspub}}
+				return o
+			},
+			wspub, wsnkp, "",
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			o := test.opts()
+			s := RunServer(o)
+			defer s.Shutdown()
+
+			wsc, br, infoMsg := testWSCreateClientGetInfo(t, false, false, o.Websocket.Host, o.Websocket.Port)
+			defer wsc.Close()
+
+			// Sign Nonce
+			var info nonceInfo
+			json.Unmarshal([]byte(infoMsg[5:]), &info)
+			sigraw, _ := test.kp.Sign([]byte(info.Nonce))
+			sig := base64.RawURLEncoding.EncodeToString(sigraw)
+
+			connectProto := fmt.Sprintf("CONNECT {\"verbose\":false,\"protocol\":1,\"nkey\":\"%s\",\"sig\":\"%s\"}\r\nPING\r\n", test.nkey, sig)
+
+			wsmsg := testWSCreateClientMsg(wsBinaryMessage, 1, true, false, []byte(connectProto))
+			if _, err := wsc.Write(wsmsg); err != nil {
+				t.Fatalf("Error sending message: %v", err)
+			}
+			msg := testWSReadFrame(t, br)
+			if test.err == "" && !bytes.HasPrefix(msg, []byte("PONG\r\n")) {
+				t.Fatalf("Expected to receive PONG, got %q", msg)
+			} else if test.err != "" && !bytes.HasPrefix(msg, []byte(test.err)) {
+				t.Fatalf("Expected to receive %q, got %q", test.err, msg)
+			}
+		})
+	}
+}
+
 // ==================================================================
 // = Benchmark tests
 // ==================================================================
diff --git a/test/configs/tls_mixed.conf b/test/configs/tls_mixed.conf
deleted file mode 100644
index 6b218fe8..00000000
--- a/test/configs/tls_mixed.conf
+++ /dev/null
@@ -1,15 +0,0 @@
-# Allow TLS and non TLS on same port.
-
-listen: 127.0.0.1:-1
-
-tls {
-  # Server cert
-  cert_file: "./configs/certs/server-cert.pem"
-  # Server private key
-  key_file:  "./configs/certs/server-key.pem"
-  # Specified time for handshake to complete
-  timeout: 2
-}
-
-# This allows non tls traffic on same port.
-allow_non_tls: true
diff --git a/test/tls_test.go b/test/tls_test.go
index b5649198..6aa4e2b6 100644
--- a/test/tls_test.go
+++ b/test/tls_test.go
@@ -859,55 +859,6 @@ func TestTLSAuthorizationShortTimeout(t *testing.T) {
 	}
 }
 
-func TestClientTLSAndNonTLSConnections(t *testing.T) {
-	s, opts := RunServerWithConfig("./configs/tls_mixed.conf")
-	defer s.Shutdown()
-
-	surl := fmt.Sprintf("tls://%s:%d", opts.Host, opts.Port)
-	nc, err := nats.Connect(surl, nats.RootCAs("./configs/certs/ca.pem"))
-	if err != nil {
-		t.Fatalf("Failed to connect with TLS: %v", err)
-	}
-	defer nc.Close()
-
-	// Now also make sure we can connect through plain text.
-	nurl := fmt.Sprintf("nats://%s:%d", opts.Host, opts.Port)
-	nc2, err := nats.Connect(nurl)
-	if err != nil {
-		t.Fatalf("Failed to connect without TLS: %v", err)
-	}
-	defer nc2.Close()
-
-	// Make sure they can go back and forth.
-	sub, err := nc.SubscribeSync("foo")
-	if err != nil {
-		t.Fatalf("Error subscribing: %v", err)
-	}
-	sub2, err := nc2.SubscribeSync("bar")
-	if err != nil {
-		t.Fatalf("Error subscribing: %v", err)
-	}
-	nc.Flush()
-	nc2.Flush()
-
-	nmsgs := 100
-	for i := 0; i < nmsgs; i++ {
-		nc2.Publish("foo", []byte("HELLO FROM PLAINTEXT"))
-		nc.Publish("bar", []byte("HELLO FROM TLS"))
-	}
-	// Now wait for the messages.
-	checkFor(t, time.Second, 10*time.Millisecond, func() error {
-		if msgs, _, err := sub.Pending(); err != nil || msgs != nmsgs {
-			return fmt.Errorf("Did not receive the correct number of messages: %d", msgs)
-		}
-		if msgs, _, err := sub2.Pending(); err != nil || msgs != nmsgs {
-			return fmt.Errorf("Did not receive the correct number of messages: %d", msgs)
-		}
-		return nil
-	})
-
-}
-
 func stressConnect(t *testing.T, wg *sync.WaitGroup, errCh chan error, url string, index int) {
 	defer wg.Done()