diff --git a/controller/config/config.go b/controller/config/config.go
index 29f8a9f28b2405298559720e474b04132357c089..eace6314509c2d0af78427e3fcaaac08deafe104 100644
--- a/controller/config/config.go
+++ b/controller/config/config.go
@@ -19,6 +19,8 @@ const (
 	defaultJWTDuration              = time.Hour * 24
 	jwtSecretKey                    = "jwtSecret"
 	gNMISubscriptionsFilePathKey    = "gNMISubscriptionsPath"
+	maxTokensPerUserKey             = "maxTokensPerUser"
+	maxTokensPerUserDefault         = 100
 
 	// RabbitMQ Broker.
 	amqpPrefixKey   = "amqpPrefix"
@@ -81,6 +83,9 @@ var CertFilePath string
 // KeyFilePath si the path to the private key that the controller should use for TLS connections.
 var KeyFilePath string
 
+// MaxTokensPerUser is the maximum number of tokens a user can have. This determiens the maximum of concurrent logged in sessions per user.
+var MaxTokensPerUser int
+
 // Init gets called on module import.
 func Init() {
 	err := InitializeConfig()
@@ -107,9 +112,9 @@ func InitializeConfig() error {
 
 	setLogLevel()
 
-	DatabaseConnection = getStringFromViper(databaseConnectionKey)
+	DatabaseConnection = viper.GetString(databaseConnectionKey)
 
-	FilesystemPathToStores = getStringFromViper(filesystemPathToStores)
+	FilesystemPathToStores = viper.GetString(filesystemPathToStores)
 	if FilesystemPathToStores == "" {
 		FilesystemPathToStores = "stores"
 	}
@@ -121,15 +126,20 @@ func InitializeConfig() error {
 
 	JWTSecret = viper.GetString(jwtSecretKey)
 
-	GNMISubscriptionsFilePath = getStringFromViper(gNMISubscriptionsFilePathKey)
+	MaxTokensPerUser = viper.GetInt(maxTokensPerUserKey)
+	if MaxTokensPerUser == 0 {
+		MaxTokensPerUser = maxTokensPerUserDefault
+	}
+
+	GNMISubscriptionsFilePath = viper.GetString(gNMISubscriptionsFilePathKey)
 
 	loadAMQPConfig()
 
-	CAFilePath = getStringFromViper(tlsCACertFileKey)
+	CAFilePath = viper.GetString(tlsCACertFileKey)
 
-	CertFilePath = getStringFromViper(tlsCertFileKey)
+	CertFilePath = viper.GetString(tlsCertFileKey)
 
-	KeyFilePath = getStringFromViper(tlsKeyFileKey)
+	KeyFilePath = viper.GetString(tlsKeyFileKey)
 
 	if err := viper.WriteConfig(); err != nil {
 		return err
@@ -161,12 +171,6 @@ func getUUIDFromViper(viperKey string) (uuid.UUID, error) {
 	return parsedUUID, nil
 }
 
-func getStringFromViper(viperKey string) string {
-	stringFromViper := viper.GetString(viperKey)
-
-	return stringFromViper
-}
-
 func setChangeTimeout() error {
 	e := os.Getenv(changeTimeoutKey)
 	if e != "" {
@@ -202,9 +206,9 @@ func getDurationFromViper(viperKey, unit string) (time.Duration, error) {
 }
 
 func loadAMQPConfig() {
-	AMQPPrefix = getStringFromViper(amqpPrefixKey)
-	AMQPUser = getStringFromViper(amqpUserKey)
-	AMQPPassword = getStringFromViper(amqpPasswordKey)
-	AMQPHost = getStringFromViper(amqpHostKey)
-	AMQPPort = getStringFromViper(amqpPortKey)
+	AMQPPrefix = viper.GetString(amqpPrefixKey)
+	AMQPUser = viper.GetString(amqpUserKey)
+	AMQPPassword = viper.GetString(amqpPasswordKey)
+	AMQPHost = viper.GetString(amqpHostKey)
+	AMQPPort = viper.GetString(amqpPortKey)
 }
diff --git a/controller/configs/basic-docker-compose.toml b/controller/configs/basic-docker-compose.toml
index 2708de32e81c6101ffcf2da807de72ee86065d4d..d14da89b04f9d107f22f9e237f0e68cf36ed2df8 100644
--- a/controller/configs/basic-docker-compose.toml
+++ b/controller/configs/basic-docker-compose.toml
@@ -16,6 +16,7 @@ plugin-folder = 'plugins'
 plugin-registry = 'plugin-registry:55057'
 security = 'insecure'
 socket = ':55055'
+maxTokensPerUser = 100
 tlscacertfile = '/ssl/ca.crt'
 tlscertfile = '/ssl/certs/gosdn-selfsigned.crt'
 tlskeyfile = '/ssl/private/gosdn-selfsigned.key'
diff --git a/controller/configs/containerlab-gosdn.toml.example b/controller/configs/containerlab-gosdn.toml.example
index e5ed6652053ef3f64a929e863cf05a8f07758032..4b3e0668ae77b65fc85b0ff2681efa018f174f67 100644
--- a/controller/configs/containerlab-gosdn.toml.example
+++ b/controller/configs/containerlab-gosdn.toml.example
@@ -10,6 +10,7 @@ gNMISubscriptionsPath = "configs/gNMISubscriptions.txt"
 tlscertfile = '/ssl/certs/gosdn-selfsigned.crt'
 tlskeyfile = '/ssl/private/gosdn-selfsigned.key'
 tlscacertfile = '/ssl/ca.crt'
+maxTokensPerUser = 100
 
 amqpPrefix = "amqp://"
 amqpUser = "guest"
diff --git a/controller/configs/development-gosdn.toml.example b/controller/configs/development-gosdn.toml.example
index 4845c082c2acb6476c48b4e313ee8578a13d3c87..56bc0f26184b2fcd9fd3e5787afe56c0ce4664e0 100644
--- a/controller/configs/development-gosdn.toml.example
+++ b/controller/configs/development-gosdn.toml.example
@@ -15,6 +15,7 @@ log-level = 'debug'
 plugin-folder = 'plugins'
 security = 'insecure'
 socket = ':55055'
+maxTokensPerUser = 100
 tlscertfile = '/ssl/certs/gosdn-selfsigned.crt'
 tlskeyfile = '/ssl/private/gosdn-selfsigned.key'
 tlscacertfile = '/ssl/ca.crt'
diff --git a/controller/configs/integration-test-gosdn.toml b/controller/configs/integration-test-gosdn.toml
index 2708de32e81c6101ffcf2da807de72ee86065d4d..d14da89b04f9d107f22f9e237f0e68cf36ed2df8 100644
--- a/controller/configs/integration-test-gosdn.toml
+++ b/controller/configs/integration-test-gosdn.toml
@@ -16,6 +16,7 @@ plugin-folder = 'plugins'
 plugin-registry = 'plugin-registry:55057'
 security = 'insecure'
 socket = ':55055'
+maxTokensPerUser = 100
 tlscacertfile = '/ssl/ca.crt'
 tlscertfile = '/ssl/certs/gosdn-selfsigned.crt'
 tlskeyfile = '/ssl/private/gosdn-selfsigned.key'
diff --git a/controller/controller.go b/controller/controller.go
index 1ae253a72db34fb7e2353b1b64cad430e65d33aa..3be1d667180a715dcc138c5b135771b4691062d8 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -10,6 +10,7 @@ import (
 	"os/signal"
 	"sync"
 	"syscall"
+	"time"
 
 	"github.com/google/uuid"
 	"github.com/sethvargo/go-password/password"
@@ -172,6 +173,10 @@ func initialize() error {
 		return err
 	}
 
+	if err := deletAllExpiredUserTokens(); err != nil {
+		return err
+	}
+
 	if err := startGrpc(); err != nil {
 		return err
 	}
@@ -441,6 +446,40 @@ func ensureDefaultUserExists() error {
 	return nil
 }
 
+func deletAllExpiredUserTokens() error {
+	// Temporarly create JWT manager just to evaluate tokens here
+	jwtManager := rbacImpl.NewJWTManager(config.JWTSecret, config.JWTDuration)
+
+	users, err := c.userService.GetAll()
+	if err != nil {
+		return fmt.Errorf("error getting all users while deleting expires user tokens: %w", err)
+	}
+	for i, user := range users {
+		tokens := user.GetTokens()
+		for _, token := range tokens {
+			claims, err := jwtManager.GetClaimsFromToken(token)
+			if err != nil {
+				return fmt.Errorf("error getting claims from token while deleting expired user tokens: %w", err)
+			}
+			if claims.ExpiresAt < time.Now().Unix() {
+				err := users[i].RemoveToken(token)
+				if err != nil {
+					return fmt.Errorf("error removing token while deleting expired user tokens: %w", err)
+				}
+			}
+		}
+	}
+
+	for _, user := range users {
+		err := c.userService.Update(user)
+		if err != nil {
+			return fmt.Errorf("error updating user while deleting expired user tokens: %w", err)
+		}
+	}
+	return nil
+
+}
+
 // Run calls initialize to start the controller.
 func Run(ctx context.Context) error {
 	var initError error