From 0122537872565c1f1eb92ed5077166ed623ba975 Mon Sep 17 00:00:00 2001
From: Fabian Seidl <fabian.b.seidl@stud.h-da.de>
Date: Tue, 24 May 2022 08:50:51 +0000
Subject: [PATCH] Improvements for RBAC

See merge request danet/gosdn!320

Co-authored-by: Andre Sterba <andre.sterba@stud.h-da.de>
---
 cli/cmd/login.go                              |   8 ++
 cli/cmd/logout.go                             |  19 ++-
 cli/cmd/userCreate.go                         |   2 -
 cli/cmd/userUpdate.go                         |   2 -
 controller/api/apiUtil_test.go                |  57 +++++++-
 controller/api/auth_test.go                   |   9 +-
 controller/api/initialise_test.go             |   1 +
 controller/api/user_test.go                   |   8 +-
 controller/cmd/root.go                        |   1 +
 controller/config/config.go                   |  36 ++++-
 controller/configs/gosdn.toml                 |   3 +
 controller/controller.go                      |  22 +--
 controller/interfaces/rbac/rbacService.go     |   1 +
 controller/interfaces/rbac/user.go            |   1 +
 controller/northbound/server/auth.go          |  91 +++++++++---
 .../northbound/server/auth_interceptor.go     |  26 ++--
 .../server/auth_interceptor_test.go           |   4 -
 controller/northbound/server/auth_test.go     | 130 +++++++++++++++++-
 controller/northbound/server/nbi.go           |   8 +-
 controller/northbound/server/pnd_test.go      |   4 +-
 controller/northbound/server/role.go          |  19 ++-
 .../northbound/server/test_util_test.go       |  41 ++++--
 controller/northbound/server/user.go          |  48 ++++---
 controller/northbound/server/user_test.go     |  13 +-
 controller/rbac/jwtManager.go                 |  10 ++
 controller/rbac/rbacService.go                |   2 +-
 controller/rbac/user.go                       |  19 ++-
 27 files changed, 461 insertions(+), 124 deletions(-)

diff --git a/cli/cmd/login.go b/cli/cmd/login.go
index 19c48e60a..77e655f81 100644
--- a/cli/cmd/login.go
+++ b/cli/cmd/login.go
@@ -57,6 +57,14 @@ var loginCmd = &cobra.Command{
 			pterm.Info.Println("New controller address: ", viper.GetString("controllerAPIEndpoint"))
 		}
 
+		// log out to remove active session in case an user is already logged in
+		if userToken != "" {
+			_, err := api.Logout(createContextWithAuthorization(), viper.GetString("controllerAPIEndpoint"), nbUserName)
+			if err != nil {
+				pterm.Error.Println("error logging out active user", err)
+			}
+		}
+
 		// TODO: maybe add credentials in context instead of context.TODO()
 		resp, err := api.Login(context.TODO(), viper.GetString("controllerAPIEndpoint"), nbUserName, nbUserPwd)
 		if err != nil {
diff --git a/cli/cmd/logout.go b/cli/cmd/logout.go
index a797c80e5..a8268b764 100644
--- a/cli/cmd/logout.go
+++ b/cli/cmd/logout.go
@@ -32,8 +32,10 @@ POSSIBILITY OF SUCH DAMAGE.
 package cmd
 
 import (
+	"time"
+
 	"code.fbi.h-da.de/danet/gosdn/controller/api"
-	log "github.com/sirupsen/logrus"
+	"github.com/pterm/pterm"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
 )
@@ -45,13 +47,24 @@ var logoutCmd = &cobra.Command{
 	Long:  `Logs the current user out. Further actions on the controller are not permitted after this.`,
 
 	RunE: func(cmd *cobra.Command, args []string) error {
+		spinner, _ := pterm.DefaultSpinner.Start("Logout attempt for user: ", nbUserName)
+
 		resp, err := api.Logout(createContextWithAuthorization(), viper.GetString("controllerAPIEndpoint"), nbUserName)
 		if err != nil {
+			spinner.Fail("Logout failed: ", err)
+			return err
+		}
+
+		userToken = ""
+
+		viper.Set("USER_TOKEN", userToken)
+		err = viper.WriteConfig()
+		if err != nil {
+			pterm.Error.Println(err)
 			return err
 		}
 
-		// TODO: unset session proof here
-		log.Info("LogoutResponse: " + resp.Status.String())
+		spinner.Success("User ", nbUserName, " successfully logged out at ", time.Unix((resp.Timestamp/1000000000), 0), ".")
 
 		return nil
 	},
diff --git a/cli/cmd/userCreate.go b/cli/cmd/userCreate.go
index 0b64cad0f..bd0b87888 100644
--- a/cli/cmd/userCreate.go
+++ b/cli/cmd/userCreate.go
@@ -53,8 +53,6 @@ var userCreateCmd = &cobra.Command{
 		// only active pnd for now, add option for additional param later
 		roles[viper.GetString("CLI_PND")] = nbUserRole
 
-		//TODO(faseid): hash password
-
 		// only one for now, add option to add more users at once later
 		users := []*apb.User{
 			{
diff --git a/cli/cmd/userUpdate.go b/cli/cmd/userUpdate.go
index a85365a5b..7288d9e4d 100644
--- a/cli/cmd/userUpdate.go
+++ b/cli/cmd/userUpdate.go
@@ -53,8 +53,6 @@ var userUpdateCmd = &cobra.Command{
 		// only active pnd for now, add option for additional param later
 		roles[viper.GetString("CLI_PND")] = nbUserRole
 
-		//TODO(faseid): hash password
-
 		// only one for now, add option to update more users at once later
 		users := []*apb.User{
 			{
diff --git a/controller/api/apiUtil_test.go b/controller/api/apiUtil_test.go
index bea1ab205..cc01aebb9 100644
--- a/controller/api/apiUtil_test.go
+++ b/controller/api/apiUtil_test.go
@@ -1,8 +1,15 @@
 package api
 
 import (
+	"encoding/base64"
+	"time"
+
+	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
 	rbacImpl "code.fbi.h-da.de/danet/gosdn/controller/rbac"
+	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
+	"github.com/sethvargo/go-password/password"
+	"golang.org/x/crypto/argon2"
 )
 
 // Name of this file requires _test at the end, because of how the availability of varibales is handled in test files of go packages.
@@ -54,18 +61,28 @@ func clearAndCreateAuthTestSetup() error {
 	return nil
 }
 
-//TODO(faseid): change password to hashed/encrypted one
 func createTestUsers() error {
 	randomRoleMap := map[string]string{pndID: randomRoleName}
 
+	// Generate a salt that is 16 characters long with 3 digits, 0 symbols,
+	// allowing upper and lower case letters, disallowing repeat characters.
+	salt, err := password.Generate(16, 3, 0, true, false)
+	if err != nil {
+		return err
+	}
+
+	testAdminPWD := createHashedAndSaltedPassword("admin", salt)
+	testUserPWD := createHashedAndSaltedPassword("user", salt)
+	testRandPWD := createHashedAndSaltedPassword("aurelius", salt)
+
 	users := []rbacImpl.User{
-		{UserID: uuid.MustParse(adminID), UserName: "testAdmin", Roles: adminRoleMap, Password: "admin"},
-		{UserID: uuid.MustParse(userID), UserName: "testUser", Roles: userRoleMap, Password: "user"},
-		{UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: "aurelius", Token: "wrong token"},
+		{UserID: uuid.MustParse(adminID), UserName: "testAdmin", Roles: adminRoleMap, Password: testAdminPWD},
+		{UserID: uuid.MustParse(userID), UserName: "testUser", Roles: userRoleMap, Password: testUserPWD},
+		{UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: testRandPWD, Token: "wrong token"},
 	}
 
 	for _, u := range users {
-		err := userService.Add(rbacImpl.NewUser(u.ID(), u.Name(), u.Roles, u.Password, ""))
+		err := userService.Add(rbacImpl.NewUser(u.ID(), u.Name(), u.Roles, u.Password, "", salt))
 		if err != nil {
 			return err
 		}
@@ -112,3 +129,33 @@ func createTestRoles() error {
 
 	return nil
 }
+
+// Creates a token to be used in auth interceptor tests. If validTokenRequired is set as true, the generated token will also
+// be attached to the provided user. Else the user won't have the token and can not be authorized.
+func createTestUserToken(userName string, validTokenRequired bool) (string, error) {
+	jwt := rbacImpl.NewJWTManager("", 1*time.Minute)
+
+	token, err := jwt.GenerateToken(rbac.User{UserName: userName})
+	if err != nil {
+		return token, err
+	}
+
+	if validTokenRequired {
+		user, err := userService.Get(store.Query{Name: userName})
+		if err != nil {
+			return token, err
+		}
+		user.SetToken(token)
+
+		err = userService.Update(user)
+		if err != nil {
+			return token, err
+		}
+	}
+
+	return token, nil
+}
+
+func createHashedAndSaltedPassword(plainPWD, salt string) string {
+	return base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(plainPWD), []byte(salt), 1, 64*1024, 4, 32))
+}
diff --git a/controller/api/auth_test.go b/controller/api/auth_test.go
index 8012d03cb..1c7cdb5af 100644
--- a/controller/api/auth_test.go
+++ b/controller/api/auth_test.go
@@ -5,6 +5,8 @@ import (
 	"testing"
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
+	log "github.com/sirupsen/logrus"
+	"google.golang.org/grpc/metadata"
 )
 
 func TestLogin(t *testing.T) {
@@ -64,6 +66,11 @@ func TestLogin(t *testing.T) {
 }
 
 func TestLogout(t *testing.T) {
+	validToken, err := createTestUserToken("testAdmin", true)
+	if err != nil {
+		log.Fatal(err)
+	}
+
 	type args struct {
 		ctx      context.Context
 		addr     string
@@ -78,7 +85,7 @@ func TestLogout(t *testing.T) {
 		{
 			name: "default log out",
 			args: args{
-				ctx:      context.TODO(),
+				ctx:      metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", validToken)),
 				addr:     testAPIEndpoint,
 				username: "testAdmin",
 			},
diff --git a/controller/api/initialise_test.go b/controller/api/initialise_test.go
index 20982ef78..8638d200c 100644
--- a/controller/api/initialise_test.go
+++ b/controller/api/initialise_test.go
@@ -144,6 +144,7 @@ func bootstrapUnitTest() {
 
 	northbound := nbi.NewNBI(pndStore, userService, roleService)
 	northbound.Auth = nbi.NewAuthServer(jwtManager)
+	northbound.User = nbi.NewUserServer(jwtManager)
 
 	cpb.RegisterCoreServiceServer(s, northbound.Core)
 	ppb.RegisterPndServiceServer(s, northbound.Pnd)
diff --git a/controller/api/user_test.go b/controller/api/user_test.go
index edfb703d3..ab8cf858e 100644
--- a/controller/api/user_test.go
+++ b/controller/api/user_test.go
@@ -7,9 +7,15 @@ import (
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
 	"github.com/google/uuid"
+	"google.golang.org/grpc/metadata"
 )
 
 func TestCreateUsers(t *testing.T) {
+	token, err := createTestUserToken("testAdmin", true)
+	if err != nil {
+		t.Fatalf("%v", err)
+	}
+
 	type args struct {
 		ctx   context.Context
 		addr  string
@@ -24,7 +30,7 @@ func TestCreateUsers(t *testing.T) {
 		{
 			name: "default create users",
 			args: args{
-				ctx:  context.TODO(),
+				ctx:  metadata.NewOutgoingContext(context.Background(), metadata.Pairs("authorize", token)),
 				addr: testAPIEndpoint,
 				users: []*apb.User{
 					{
diff --git a/controller/cmd/root.go b/controller/cmd/root.go
index c1dad9408..6149c2aff 100644
--- a/controller/cmd/root.go
+++ b/controller/cmd/root.go
@@ -126,6 +126,7 @@ func initConfig() {
 	viper.SetDefault("csbi-orchestrator", "localhost:55056")
 	viper.SetDefault("plugin-folder", "plugins")
 	viper.SetDefault("security", "secure")
+	viper.SetDefault("defaultJWTDuration", 24)
 
 	ll := viper.GetString("GOSDN_LOG")
 	if ll != "" {
diff --git a/controller/config/config.go b/controller/config/config.go
index ac47b9541..059821cf7 100644
--- a/controller/config/config.go
+++ b/controller/config/config.go
@@ -18,6 +18,9 @@ const (
 	changeTimeoutKey                = "GOSDN_CHANGE_TIMEOUT"
 	databaseConnectionKey           = "databaseConnection"
 	filesystemPathToStores          = "filesystemPathToStores"
+	jwtDurationKey                  = "defaultJWTDuration"
+	defaultJWTDuration              = time.Hour * 24
+	jwtSecretKey                    = "jwtSecret"
 )
 
 // BasePndUUID is an uuid for the base PND
@@ -41,6 +44,12 @@ var DatabaseConnection string
 // FilesystemPathToStores determines in which folder the stores should be saved
 var FilesystemPathToStores = "stores_testing"
 
+// JWTDuration determines how long a jwt is valid
+var JWTDuration time.Duration
+
+// JWTSecret determines the scret that is used to sign tokens
+var JWTSecret string
+
 // Init gets called on module import
 func Init() {
 	err := InitializeConfig()
@@ -71,9 +80,6 @@ func InitializeConfig() error {
 	if BaseSouthBoundType != 1 {
 		BaseSouthBoundType = 1
 		viper.Set(baseSouthBoundTypeKey, 1)
-		if err := viper.WriteConfig(); err != nil {
-			return err
-		}
 	}
 
 	err = setChangeTimeout()
@@ -90,6 +96,17 @@ func InitializeConfig() error {
 		FilesystemPathToStores = "stores"
 	}
 
+	JWTDuration, err = getDurationFromViper(jwtDurationKey, time.Hour)
+	if err != nil {
+		JWTDuration = defaultJWTDuration
+	}
+
+	JWTSecret = viper.GetString(jwtSecretKey)
+
+	if err := viper.WriteConfig(); err != nil {
+		return err
+	}
+
 	return nil
 }
 
@@ -104,9 +121,6 @@ func getUUIDFromViper(viperKey string) (uuid.UUID, error) {
 	if UUIDAsString == "" {
 		newUUID := uuid.New()
 		viper.Set(viperKey, newUUID.String())
-		if err := viper.WriteConfig(); err != nil {
-			return uuid.Nil, err
-		}
 
 		return newUUID, nil
 	}
@@ -147,3 +161,13 @@ func setLogLevel() {
 		LogLevel = logrus.InfoLevel
 	}
 }
+
+func getDurationFromViper(viperKey string, unit time.Duration) (time.Duration, error) {
+	duration := viper.GetDuration(viperKey)
+
+	if duration > 0 {
+		return duration * unit, nil
+	}
+
+	return 0, viper.ConfigParseError{}
+}
diff --git a/controller/configs/gosdn.toml b/controller/configs/gosdn.toml
index dc3060edb..d215797b3 100644
--- a/controller/configs/gosdn.toml
+++ b/controller/configs/gosdn.toml
@@ -1,3 +1,6 @@
+# This is used as an example config or when the controller gets started using the config path flag.
+# If the controller is started from gosdn/controller directory the config file that gets used can
+# can be found in controller/cmd/gosdn/configs/gosdn.toml
 basepnduuid = "5f20f34b-cbd0-4511-9ddc-c50cf6a3b49d"
 basesouthboundtype = 1
 basesouthbounduuid = "ca29311a-3b17-4385-96f8-515b602a97ac"
diff --git a/controller/controller.go b/controller/controller.go
index c3c49d2cc..eebc8ae8c 100644
--- a/controller/controller.go
+++ b/controller/controller.go
@@ -2,6 +2,7 @@ package controller
 
 import (
 	"context"
+	"encoding/base64"
 	"fmt"
 	"net"
 	"net/http"
@@ -9,12 +10,12 @@ import (
 	"os/signal"
 	"sync"
 	"syscall"
-	"time"
 
 	"github.com/google/uuid"
 	"github.com/sethvargo/go-password/password"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/viper"
+	"golang.org/x/crypto/argon2"
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/credentials/insecure"
 
@@ -78,11 +79,6 @@ func initialize() error {
 	startHttpServer()
 	coreLock.Unlock()
 
-	err = config.InitializeConfig()
-	if err != nil {
-		return err
-	}
-
 	err = createPrincipalNetworkDomain()
 	if err != nil {
 		return err
@@ -109,11 +105,12 @@ func startGrpc() error {
 	}
 	log.Infof("listening to %v", lislisten.Addr())
 
-	jwtManager := rbacImpl.NewJWTManager("", (10000 * time.Hour)) //TODO(faseid): add real secret and proper duration data here!
+	jwtManager := rbacImpl.NewJWTManager(config.JWTSecret, config.JWTDuration)
 	setupGRPCServerWithCorrectSecurityLevel(jwtManager)
 
 	c.nbi = nbi.NewNBI(c.pndStore, c.userService, c.roleService)
 	c.nbi.Auth = nbi.NewAuthServer(jwtManager)
+	c.nbi.User = nbi.NewUserServer(jwtManager)
 
 	pb.RegisterCoreServiceServer(c.grpcServer, c.nbi.Core)
 	ppb.RegisterPndServiceServer(c.grpcServer, c.nbi.Pnd)
@@ -226,12 +223,19 @@ func ensureDefaultUserExists() error {
 			log.Fatal(err)
 		}
 
-		fmt.Printf("########\n Generated admin password: %s\n########\n", generatedPassword)
+		salt, err := password.Generate(16, 3, 0, true, false)
+		if err != nil {
+			log.Fatal(err)
+		}
+
+		hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(generatedPassword), []byte(salt), 1, 64*1024, 4, 32))
 
-		err = c.userService.Add(rbacImpl.NewUser(uuid.New(), defaultUserName, map[string]string{config.BasePndUUID.String(): "admin"}, generatedPassword, ""))
+		err = c.userService.Add(rbacImpl.NewUser(uuid.New(), defaultUserName, map[string]string{config.BasePndUUID.String(): "admin"}, string(hashedPassword), "", salt))
 		if err != nil {
 			return err
 		}
+
+		fmt.Printf("########\n Generated admin password: %s\n########\n", generatedPassword)
 	}
 
 	return nil
diff --git a/controller/interfaces/rbac/rbacService.go b/controller/interfaces/rbac/rbacService.go
index e5a543e8c..74c938ac2 100644
--- a/controller/interfaces/rbac/rbacService.go
+++ b/controller/interfaces/rbac/rbacService.go
@@ -20,6 +20,7 @@ type LoadedUser struct {
 	Roles    map[string]string `json:"roles,omitempty"`
 	Password string            `json:"password"`
 	Token    string            `json:"token,omitempty"`
+	Salt     string            `json:"salt" bson:"salt"`
 }
 
 // RoleService describes an interface for role service implementations.
diff --git a/controller/interfaces/rbac/user.go b/controller/interfaces/rbac/user.go
index 8ed0fc254..37b4f9021 100644
--- a/controller/interfaces/rbac/user.go
+++ b/controller/interfaces/rbac/user.go
@@ -10,4 +10,5 @@ type User interface {
 	GetPassword() string
 	GetToken() string
 	SetToken(string)
+	GetSalt() string
 }
diff --git a/controller/northbound/server/auth.go b/controller/northbound/server/auth.go
index 4507b5f1d..bf0096873 100644
--- a/controller/northbound/server/auth.go
+++ b/controller/northbound/server/auth.go
@@ -2,6 +2,7 @@ package server
 
 import (
 	"context"
+	"encoding/base64"
 	"time"
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
@@ -9,7 +10,9 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/prometheus/client_golang/prometheus"
+	"golang.org/x/crypto/argon2"
 	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/status"
 )
 
@@ -37,11 +40,9 @@ func (s Auth) Login(ctx context.Context, request *apb.LoginRequest) (*apb.LoginR
 		Password: request.Pwd,
 	}
 
-	//TODO: add check if user is logged in with session handling
-
 	// validation of credentials
-	validCredentials, err := s.isValidUser(user)
-	if (err != nil) || !validCredentials {
+	err := s.isValidUser(user)
+	if err != nil {
 		return nil, err
 	}
 
@@ -51,14 +52,14 @@ func (s Auth) Login(ctx context.Context, request *apb.LoginRequest) (*apb.LoginR
 		return nil, err
 	}
 
-	userToUpdate, err := userc.Get(store.Query{Name: user.UserName})
+	userToUpdate, err := userService.Get(store.Query{Name: user.UserName})
 	if err != nil {
 		return nil, err
 	}
 
 	userToUpdate.SetToken(token)
 
-	err = userc.Update(userToUpdate)
+	err = userService.Update(userToUpdate)
 	if err != nil {
 		return nil, err
 	}
@@ -76,8 +77,10 @@ func (s Auth) Logout(ctx context.Context, request *apb.LogoutRequest) (*apb.Logo
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	// not implemented yet
-	// TODO: add session handling and logout
+	err := s.handleLogout(ctx, request.Username)
+	if err != nil {
+		return nil, err
+	}
 
 	return &apb.LogoutResponse{
 		Timestamp: time.Now().UnixNano(),
@@ -85,19 +88,75 @@ func (s Auth) Logout(ctx context.Context, request *apb.LogoutRequest) (*apb.Logo
 	}, nil
 }
 
-func (s Auth) isValidUser(user rbac.User) (bool, error) {
-	storedUser, err := userc.Get(store.Query{Name: user.Name()})
+// isValidUser checks if the provided user name fits to a stored one and then checks if the provided password is correct.
+func (s Auth) isValidUser(user rbac.User) error {
+	storedUser, err := userService.Get(store.Query{Name: user.Name()})
 	if err != nil {
-		return false, err
-	} else if storedUser == nil {
-		return false, status.Errorf(codes.Aborted, "no user object")
+		return err
 	}
 
 	if storedUser.Name() == user.Name() {
-		if storedUser.GetPassword() == user.GetPassword() {
-			return true, nil
+		err := s.isCorrectPassword(storedUser.GetPassword(), storedUser.GetSalt(), user.Password)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// isCorrectPassword checks if the provided password fits with the hashed user password taken from the storage.
+func (s Auth) isCorrectPassword(storedPassword, salt, loginPassword string) error {
+	hashedPasswordFromLogin := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(loginPassword), []byte(salt), 1, 64*1024, 4, 32))
+
+	if storedPassword == hashedPasswordFromLogin {
+		return nil
+	}
+
+	return status.Errorf(codes.Unauthenticated, "incorrect user name or password")
+}
+
+// handleLogout checks if the provided user name matches with the one associated with token and
+// replaces the stored token of the user with an empty string.
+func (s Auth) handleLogout(ctx context.Context, userName string) error {
+	md, ok := metadata.FromIncomingContext(ctx)
+	if !ok {
+		return status.Errorf(codes.Aborted, "metadata is not provided")
+	}
+
+	if len(md["authorize"]) > 0 {
+		token := md["authorize"][0]
+
+		claims, err := s.jwtManager.GetClaimsFromToken(token)
+		if err != nil {
+			return err
+		}
+
+		if claims.Username != userName {
+			return status.Errorf(codes.Aborted, "missing match of user associated to token and provided user name")
+		}
+
+		storedUser, err := userService.Get(store.Query{Name: userName})
+		if err != nil {
+			return err
+		}
+
+		if token != storedUser.GetToken() {
+			return status.Errorf(codes.Aborted, "missing match of token provied for user")
+		}
+
+		err = userService.Update(&rbac.User{UserID: storedUser.ID(),
+			UserName: storedUser.Name(),
+			Roles:    storedUser.GetRoles(),
+			Password: storedUser.GetPassword(),
+			Token:    " ",
+			Salt:     storedUser.GetSalt(),
+		})
+
+		if err != nil {
+			return err
 		}
 	}
 
-	return false, status.Errorf(codes.Unauthenticated, "incorrect user name or password")
+	return nil
 }
diff --git a/controller/northbound/server/auth_interceptor.go b/controller/northbound/server/auth_interceptor.go
index 47c80e023..9c5f7a746 100644
--- a/controller/northbound/server/auth_interceptor.go
+++ b/controller/northbound/server/auth_interceptor.go
@@ -2,6 +2,7 @@ package server
 
 import (
 	"context"
+	"time"
 
 	csbipb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/csbi"
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
@@ -28,20 +29,11 @@ func NewAuthInterceptor(jwtManager *rbac.JWTManager) *AuthInterceptor {
 // Unary returns a unary interceptor function to authenticate and authorize unary RPC calls
 func (auth *AuthInterceptor) Unary() grpc.UnaryServerInterceptor {
 	return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
-		switch r := req.(type) {
+		switch req.(type) {
 		case *apb.LoginRequest:
 			return handler(ctx, req)
 		case *apb.LogoutRequest:
 			return handler(ctx, req)
-		case *apb.CreateUsersRequest:
-			if len(r.User) < 2 {
-				return handler(ctx, req)
-			}
-
-			err := auth.authorize(ctx, info.FullMethod)
-			if err != nil {
-				return nil, err
-			}
 		case *csbipb.Syn:
 			return handler(ctx, req)
 		default:
@@ -79,16 +71,18 @@ func (auth *AuthInterceptor) authorize(ctx context.Context, method string) error
 	}
 
 	// validate token and check permission here
-	token := ""
 	if len(md["authorize"]) > 0 {
-		token = md["authorize"][0]
-
-		claims, err := auth.jwtManager.VerifyToken(token)
+		token := md["authorize"][0]
+		claims, err := auth.jwtManager.GetClaimsFromToken(token)
 		if err != nil {
 			return err
 		}
 
-		user, err := userc.Get(store.Query{Name: claims.Username})
+		if time.Now().Unix() > claims.ExpiresAt {
+			return status.Errorf(codes.PermissionDenied, "token expired at %v, please login", time.Unix(claims.ExpiresAt, 0))
+		}
+
+		user, err := userService.Get(store.Query{Name: claims.Username})
 		if err != nil {
 			return err
 		}
@@ -120,7 +114,7 @@ func (auth *AuthInterceptor) verifyPermisisonForRequestedCall(userRoles map[stri
 }
 
 func (auth *AuthInterceptor) verifyUserRoleAndRequestedCall(userRole, requestedMethod string) error {
-	storedRoles, err := rolec.GetAll()
+	storedRoles, err := roleService.GetAll()
 	if err != nil {
 		return err
 	}
diff --git a/controller/northbound/server/auth_interceptor_test.go b/controller/northbound/server/auth_interceptor_test.go
index 2d975d919..499a63b2c 100644
--- a/controller/northbound/server/auth_interceptor_test.go
+++ b/controller/northbound/server/auth_interceptor_test.go
@@ -2,7 +2,6 @@ package server
 
 import (
 	"context"
-	"fmt"
 	"log"
 	"net"
 	"testing"
@@ -197,9 +196,6 @@ func TestAuthInterceptor_authorize(t *testing.T) {
 		log.Fatal(err)
 	}
 
-	md := metadata.Pairs("authorize", validToken)
-	fmt.Println(md.Get("authorize"))
-
 	type args struct {
 		ctx    context.Context
 		method string
diff --git a/controller/northbound/server/auth_test.go b/controller/northbound/server/auth_test.go
index 0c2577624..9a875ec84 100644
--- a/controller/northbound/server/auth_test.go
+++ b/controller/northbound/server/auth_test.go
@@ -2,9 +2,12 @@ package server
 
 import (
 	"context"
+	"log"
 	"testing"
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
+	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
+	"google.golang.org/grpc/metadata"
 )
 
 func TestAuth_Login(t *testing.T) {
@@ -64,6 +67,11 @@ func TestAuth_Login(t *testing.T) {
 }
 
 func TestAuth_Logout(t *testing.T) {
+	validToken, err := createTestUserToken("testAdmin", true)
+	if err != nil {
+		log.Fatal(err)
+	}
+
 	type args struct {
 		ctx     context.Context
 		request *apb.LogoutRequest
@@ -77,7 +85,7 @@ func TestAuth_Logout(t *testing.T) {
 		{
 			name: "default log out",
 			args: args{
-				ctx: context.TODO(),
+				ctx: metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", validToken)),
 				request: &apb.LogoutRequest{
 					Username: "testAdmin",
 				},
@@ -91,7 +99,9 @@ func TestAuth_Logout(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			s := Auth{}
+			s := Auth{
+				jwtManager: jwt,
+			}
 			got, err := s.Logout(tt.args.ctx, tt.args.request)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("Auth.Logout() error = %v, wantErr %v", err, tt.wantErr)
@@ -104,3 +114,119 @@ func TestAuth_Logout(t *testing.T) {
 		})
 	}
 }
+
+func TestAuth_isValidUser(t *testing.T) {
+	type args struct {
+		user rbac.User
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "default valid user",
+			args: args{
+				user: rbac.User{
+					UserName: "testAdmin",
+					Password: "admin",
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "error wrong user name",
+			args: args{
+				user: rbac.User{
+					UserName: "foo",
+					Password: "admin",
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "error wrong password",
+			args: args{
+				user: rbac.User{
+					UserName: "testAdmin",
+					Password: "foo",
+				},
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := Auth{}
+			if err := s.isValidUser(tt.args.user); (err != nil) != tt.wantErr {
+				t.Errorf("Auth.isValidUser() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
+
+func TestAuth_handleLogout(t *testing.T) {
+	validToken, err := createTestUserToken("testAdmin", true)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	invalidToken, err := createTestUserToken("testAdmin", false)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	type args struct {
+		ctx      context.Context
+		userName string
+	}
+	tests := []struct {
+		name    string
+		args    args
+		wantErr bool
+	}{
+		{
+			name: "default handle logout",
+			args: args{
+				ctx:      metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", validToken)),
+				userName: "testAdmin",
+			},
+			wantErr: false,
+		},
+		{
+			name: "fail no metadata",
+			args: args{
+				ctx:      context.TODO(),
+				userName: "testAdmin",
+			},
+			wantErr: true,
+		},
+		{
+			name: "fail invalid token for user",
+			args: args{
+				ctx:      metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", invalidToken)),
+				userName: "testAdmin",
+			},
+			wantErr: true,
+		},
+		{
+			name: "fail invalid user for token",
+			args: args{
+				ctx:      metadata.NewIncomingContext(context.Background(), metadata.Pairs("authorize", validToken)),
+				userName: "testUser",
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := Auth{
+				jwtManager: jwt,
+			}
+			if err := s.handleLogout(tt.args.ctx, tt.args.userName); (err != nil) != tt.wantErr {
+				t.Errorf("Auth.handleLogout() error = %v, wantErr %v", err, tt.wantErr)
+			}
+		})
+	}
+}
diff --git a/controller/northbound/server/nbi.go b/controller/northbound/server/nbi.go
index 8df59768e..040407e32 100644
--- a/controller/northbound/server/nbi.go
+++ b/controller/northbound/server/nbi.go
@@ -11,8 +11,8 @@ import (
 )
 
 var pndc networkdomain.PndStore
-var userc rbac.UserService
-var rolec rbac.RoleService
+var userService rbac.UserService
+var roleService rbac.RoleService
 
 // NorthboundInterface is the representation of the
 // gRPC services used provided.
@@ -29,8 +29,8 @@ type NorthboundInterface struct {
 // NewNBI receives a PndStore and returns a new gRPC *NorthboundInterface
 func NewNBI(pnds networkdomain.PndStore, users rbac.UserService, roles rbac.RoleService) *NorthboundInterface {
 	pndc = pnds
-	userc = users
-	rolec = roles
+	userService = users
+	roleService = roles
 	return &NorthboundInterface{
 		Pnd:  &pndServer{},
 		Core: &core{},
diff --git a/controller/northbound/server/pnd_test.go b/controller/northbound/server/pnd_test.go
index 2aabdce13..58771744d 100644
--- a/controller/northbound/server/pnd_test.go
+++ b/controller/northbound/server/pnd_test.go
@@ -121,8 +121,8 @@ func TestMain(m *testing.M) {
 	}
 
 	// everyting auth related
-	userc = rbac.NewUserService(rbac.NewMemoryUserStore())
-	rolec = rbac.NewRoleService(rbac.NewMemoryRoleStore())
+	userService = rbac.NewUserService(rbac.NewMemoryUserStore())
+	roleService = rbac.NewRoleService(rbac.NewMemoryRoleStore())
 	err = clearAndCreateAuthTestSetup()
 	if err != nil {
 		log.Fatal(err)
diff --git a/controller/northbound/server/role.go b/controller/northbound/server/role.go
index df2f3a0f8..4f4990d0f 100644
--- a/controller/northbound/server/role.go
+++ b/controller/northbound/server/role.go
@@ -37,7 +37,7 @@ func (r Role) CreateRoles(ctx context.Context, request *apb.CreateRolesRequest)
 	for _, r := range request.Roles {
 		role := rbac.NewRole(uuid.New(), r.Name, r.Description, r.Permissions)
 
-		err := rolec.Add(role)
+		err := roleService.Add(role)
 		if err != nil {
 			log.Error(err)
 			return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -56,7 +56,7 @@ func (r Role) GetRole(ctx context.Context, request *apb.GetRoleRequest) (*apb.Ge
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	roleData, err := rolec.Get(store.Query{Name: request.RoleName})
+	roleData, err := roleService.Get(store.Query{Name: request.RoleName})
 	if err != nil {
 		return nil, err
 	}
@@ -81,7 +81,7 @@ func (r Role) GetRoles(ctx context.Context, request *apb.GetRolesRequest) (*apb.
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	roleList, err := rolec.GetAll()
+	roleList, err := roleService.GetAll()
 	if err != nil {
 		return nil, err
 	}
@@ -109,20 +109,19 @@ func (r Role) UpdateRoles(ctx context.Context, request *apb.UpdateRolesRequest)
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	// TODO: check if current user is allowed to update the role they try to update; only their own if not admin
 	for _, r := range request.Roles {
 		rid, err := uuid.Parse(r.Id)
 		if err != nil {
 			return nil, handleRPCError(labels, err)
 		}
 
-		_, err = rolec.Get(store.Query{ID: rid})
+		_, err = roleService.Get(store.Query{ID: rid})
 		if err != nil {
 			return nil, status.Errorf(codes.Canceled, "role not found %v", err)
 		}
 
 		roleToUpdate := rbac.NewRole(rid, r.Name, r.Description, r.Permissions)
-		err = rolec.Update(roleToUpdate)
+		err = roleService.Update(roleToUpdate)
 		if err != nil {
 			return nil, status.Errorf(codes.Aborted, "could not update role %v", err)
 		}
@@ -140,7 +139,7 @@ func (r Role) DeletePermissionsForRole(ctx context.Context, request *apb.DeleteP
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	roleToUpdate, err := rolec.Get(store.Query{Name: request.RoleName})
+	roleToUpdate, err := roleService.Get(store.Query{Name: request.RoleName})
 	if err != nil {
 		return nil, status.Errorf(codes.Canceled, "role not found %v", err)
 	}
@@ -165,7 +164,7 @@ func (r Role) DeletePermissionsForRole(ctx context.Context, request *apb.DeleteP
 
 	// updates the existing role with the trimmed set of permissions
 	roleToUpdate.RemovePermissionsFromRole(request.PermissionsToDelete)
-	err = rolec.Update(roleToUpdate)
+	err = roleService.Update(roleToUpdate)
 	if err != nil {
 		return nil, status.Errorf(codes.Aborted, "could not update role %v", err)
 	}
@@ -183,12 +182,12 @@ func (r Role) DeleteRoles(ctx context.Context, request *apb.DeleteRolesRequest)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
 	for _, r := range request.RoleName {
-		roleToDelete, err := rolec.Get(store.Query{Name: r})
+		roleToDelete, err := roleService.Get(store.Query{Name: r})
 		if err != nil {
 			return nil, status.Errorf(codes.Canceled, "role not found")
 		}
 
-		err = rolec.Delete(roleToDelete)
+		err = roleService.Delete(roleToDelete)
 		if err != nil {
 			return nil, status.Errorf(codes.Aborted, "error deleting role %v", err)
 		}
diff --git a/controller/northbound/server/test_util_test.go b/controller/northbound/server/test_util_test.go
index 96992775c..6eb5c982b 100644
--- a/controller/northbound/server/test_util_test.go
+++ b/controller/northbound/server/test_util_test.go
@@ -2,12 +2,15 @@ package server
 
 import (
 	"bytes"
+	"encoding/base64"
 	"log"
 	"testing"
 
 	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
+	"github.com/sethvargo/go-password/password"
+	"golang.org/x/crypto/argon2"
 )
 
 // Name of this file requires _test at the end, because of how the availability of varibales is handled in test files of go packages.
@@ -24,23 +27,23 @@ var jwt *rbac.JWTManager
 
 func clearAndCreateAuthTestSetup() error {
 	//clear setup if changed
-	storedUsers, err := userc.GetAll()
+	storedUsers, err := userService.GetAll()
 	if err != nil {
 		return err
 	}
 	for _, u := range storedUsers {
-		err = userc.Delete(u)
+		err = userService.Delete(u)
 		if err != nil {
 			return err
 		}
 	}
 
-	storedRoles, err := rolec.GetAll()
+	storedRoles, err := roleService.GetAll()
 	if err != nil {
 		return err
 	}
 	for _, r := range storedRoles {
-		err = rolec.Delete(r)
+		err = roleService.Delete(r)
 		if err != nil {
 			return err
 		}
@@ -60,18 +63,28 @@ func clearAndCreateAuthTestSetup() error {
 	return nil
 }
 
-//TODO(faseid): change password to hashed/encrypted one
 func createTestUsers() error {
 	randomRoleMap := map[string]string{pndID: randomRoleName}
 
+	// Generate a salt that is 16 characters long with 3 digits, 0 symbols,
+	// allowing upper and lower case letters, disallowing repeat characters.
+	salt, err := password.Generate(16, 3, 0, true, false)
+	if err != nil {
+		return err
+	}
+
+	testAdminPWD := createHashedAndSaltedPassword("admin", salt)
+	testUserPWD := createHashedAndSaltedPassword("user", salt)
+	testRandPWD := createHashedAndSaltedPassword("aurelius", salt)
+
 	users := []rbac.User{
-		{UserID: uuid.MustParse(adminID), UserName: "testAdmin", Roles: adminRoleMap, Password: "admin"},
-		{UserID: uuid.MustParse(userID), UserName: "testUser", Roles: userRoleMap, Password: "user"},
-		{UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: "aurelius", Token: "wrong token"},
+		{UserID: uuid.MustParse(adminID), UserName: "testAdmin", Roles: adminRoleMap, Password: testAdminPWD},
+		{UserID: uuid.MustParse(userID), UserName: "testUser", Roles: userRoleMap, Password: testUserPWD},
+		{UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: testRandPWD, Token: "wrong token"},
 	}
 
 	for _, u := range users {
-		err := userc.Add(rbac.NewUser(u.ID(), u.Name(), u.Roles, u.Password, ""))
+		err := userService.Add(rbac.NewUser(u.ID(), u.Name(), u.Roles, u.Password, "", salt))
 		if err != nil {
 			return err
 		}
@@ -112,7 +125,7 @@ func createTestRoles() error {
 	}
 
 	for _, r := range roles {
-		err := rolec.Add(rbac.NewRole(r.ID(), r.Name(), r.Description, r.Permissions))
+		err := roleService.Add(rbac.NewRole(r.ID(), r.Name(), r.Description, r.Permissions))
 		if err != nil {
 			return err
 		}
@@ -147,13 +160,13 @@ func createTestUserToken(userName string, validTokenRequired bool) (string, erro
 	}
 
 	if validTokenRequired {
-		user, err := userc.Get(store.Query{Name: userName})
+		user, err := userService.Get(store.Query{Name: userName})
 		if err != nil {
 			return token, err
 		}
 		user.SetToken(token)
 
-		err = userc.Update(user)
+		err = userService.Update(user)
 		if err != nil {
 			return token, err
 		}
@@ -161,3 +174,7 @@ func createTestUserToken(userName string, validTokenRequired bool) (string, erro
 
 	return token, nil
 }
+
+func createHashedAndSaltedPassword(plainPWD, salt string) string {
+	return base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(plainPWD), []byte(salt), 1, 64*1024, 4, 32))
+}
diff --git a/controller/northbound/server/user.go b/controller/northbound/server/user.go
index e5f5e4a73..f15f34124 100644
--- a/controller/northbound/server/user.go
+++ b/controller/northbound/server/user.go
@@ -2,7 +2,7 @@ package server
 
 import (
 	"context"
-	"fmt"
+	"encoding/base64"
 	"time"
 
 	apb "code.fbi.h-da.de/danet/gosdn/api/go/gosdn/rbac"
@@ -11,9 +11,12 @@ import (
 	"code.fbi.h-da.de/danet/gosdn/controller/store"
 	"github.com/google/uuid"
 	"github.com/prometheus/client_golang/prometheus"
+	"github.com/sethvargo/go-password/password"
 	log "github.com/sirupsen/logrus"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/status"
+
+	"golang.org/x/crypto/argon2"
 )
 
 // User holds a JWTManager and represents a UserServiceServer.
@@ -35,12 +38,9 @@ func (u User) CreateUsers(ctx context.Context, request *apb.CreateUsersRequest)
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	// TODO: implement check if user is allowed to create users with this role
-	// e.g. non-admin shouldn't be allowed to create admin users
 	for _, u := range request.User {
 		roles := map[string]string{}
 		for key, elem := range u.Roles {
-			fmt.Printf("k: %v v: %v\n", key, elem)
 			_, err := uuid.Parse(key)
 			if err != nil {
 				return nil, handleRPCError(labels, err)
@@ -48,8 +48,18 @@ func (u User) CreateUsers(ctx context.Context, request *apb.CreateUsersRequest)
 			roles[key] = elem
 		}
 
-		user := rbac.NewUser(uuid.New(), u.Name, roles, u.Password, u.Token)
-		err := userc.Add(user)
+		// Generate a salt that is 16 characters long with 3 digits, 0 symbols,
+		// allowing upper and lower case letters, disallowing repeat characters.
+		salt, err := password.Generate(16, 3, 0, true, false)
+		if err != nil {
+			log.Error(err)
+			return nil, status.Errorf(codes.Aborted, "%v", err)
+		}
+
+		hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(u.Password), []byte(salt), 1, 64*1024, 4, 32))
+
+		user := rbac.NewUser(uuid.New(), u.Name, roles, string(hashedPassword), u.Token, salt)
+		err = userService.Add(user)
 		if err != nil {
 			log.Error(err)
 			return nil, status.Errorf(codes.Aborted, "%v", err)
@@ -68,8 +78,7 @@ func (u User) GetUser(ctx context.Context, request *apb.GetUserRequest) (*apb.Ge
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	// TODO: implement check if user is allowed to get this user data; only their own if not admin
-	userData, err := userc.Get(store.Query{Name: request.Name})
+	userData, err := userService.Get(store.Query{Name: request.Name})
 	if err != nil {
 		return nil, err
 	}
@@ -93,7 +102,7 @@ func (u User) GetUsers(ctx context.Context, request *apb.GetUsersRequest) (*apb.
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	userList, err := userc.GetAll()
+	userList, err := userService.GetAll()
 	if err != nil {
 		return nil, err
 	}
@@ -120,21 +129,22 @@ func (u User) UpdateUsers(ctx context.Context, request *apb.UpdateUsersRequest)
 	start := metrics.StartHook(labels, grpcRequestsTotal)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
-	// TODO: check if current user is allowed to update the user they try to update; only their own if not admin
 	for _, u := range request.User {
 		uid, err := uuid.Parse(u.Id)
 		if err != nil {
 			return nil, handleRPCError(labels, err)
 		}
 
-		_, err = userc.Get(store.Query{ID: uid})
+		storedUser, err := userService.Get(store.Query{ID: uid})
 		if err != nil {
 			return nil, status.Errorf(codes.Canceled, "user not found %v", err)
 		}
 
-		userToUpdate := rbac.NewUser(uid, u.Name, u.Roles, u.Password, u.Token)
+		hashedPassword := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(u.Password), []byte(storedUser.GetSalt()), 1, 64*1024, 4, 32))
+
+		userToUpdate := rbac.NewUser(uid, u.Name, u.Roles, string(hashedPassword), u.Token, storedUser.GetSalt())
 
-		err = userc.Update(userToUpdate)
+		err = userService.Update(userToUpdate)
 		if err != nil {
 			return nil, status.Errorf(codes.Aborted, "could not update user %v", err)
 		}
@@ -153,12 +163,12 @@ func (u User) DeleteUsers(ctx context.Context, request *apb.DeleteUsersRequest)
 	defer metrics.FinishHook(labels, start, grpcRequestDurationSecondsTotal, grpcRequestDurationSeconds)
 
 	for _, u := range request.Username {
-		userToDelete, err := userc.Get(store.Query{Name: u})
+		userToDelete, err := userService.Get(store.Query{Name: u})
 		if err != nil {
 			return nil, status.Errorf(codes.Canceled, "user not found %v", err)
 		}
 
-		err = userc.Delete(userToDelete)
+		err = userService.Delete(userToDelete)
 		if err != nil {
 			return nil, status.Errorf(codes.Aborted, "error deleting user %v", err)
 		}
@@ -170,7 +180,7 @@ func (u User) DeleteUsers(ctx context.Context, request *apb.DeleteUsersRequest)
 }
 
 func (u User) isValidUser(user rbac.User) (bool, error) {
-	storedUser, err := userc.Get(store.Query{Name: user.Name()})
+	storedUser, err := userService.Get(store.Query{Name: user.Name()})
 	if err != nil {
 		return false, err
 	} else if storedUser == nil {
@@ -178,7 +188,11 @@ func (u User) isValidUser(user rbac.User) (bool, error) {
 	}
 
 	if storedUser.Name() == user.Name() {
-		if storedUser.GetPassword() == user.GetPassword() {
+		salt := storedUser.GetSalt()
+
+		hashedPasswordFromLogin := base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(user.GetPassword()), []byte(salt), 1, 64*1024, 4, 32))
+
+		if storedUser.GetPassword() == hashedPasswordFromLogin {
 			return true, nil
 		}
 	}
diff --git a/controller/northbound/server/user_test.go b/controller/northbound/server/user_test.go
index 6611486e1..a5d2d25f2 100644
--- a/controller/northbound/server/user_test.go
+++ b/controller/northbound/server/user_test.go
@@ -22,13 +22,14 @@ func TestUser_CreateUsers(t *testing.T) {
 	}{
 		{
 			name: "default create users",
-			args: args{ctx: context.TODO(),
+			args: args{
+				ctx: context.TODO(),
 				request: &apb.CreateUsersRequest{
 					User: []*apb.User{
 						{
-							Name:     "asdf",
-							Roles:    map[string]string{pndID: "asdf"},
-							Password: "asdf",
+							Name:     "someUser",
+							Roles:    map[string]string{pndID: "userTestRole"},
+							Password: "pass",
 							Token:    "",
 						},
 					},
@@ -40,7 +41,9 @@ func TestUser_CreateUsers(t *testing.T) {
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			s := User{}
+			s := User{
+				jwtManager: jwt,
+			}
 			got, err := s.CreateUsers(tt.args.ctx, tt.args.request)
 			if (err != nil) != tt.wantErr {
 				t.Errorf("User.CreateUsers() error = %v, wantErr %v", err, tt.wantErr)
diff --git a/controller/rbac/jwtManager.go b/controller/rbac/jwtManager.go
index 981310821..3e76ec038 100644
--- a/controller/rbac/jwtManager.go
+++ b/controller/rbac/jwtManager.go
@@ -64,3 +64,13 @@ func (man *JWTManager) VerifyToken(accessToken string) (*UserClaims, error) {
 
 	return claims, nil
 }
+
+// GetClaimsFromToken returns the UserClaims associated to the provided token.
+func (man *JWTManager) GetClaimsFromToken(token string) (*UserClaims, error) {
+	claims, err := man.VerifyToken(token)
+	if err != nil {
+		return nil, err
+	}
+
+	return claims, nil
+}
diff --git a/controller/rbac/rbacService.go b/controller/rbac/rbacService.go
index e6f000c3d..1cbb6f94d 100644
--- a/controller/rbac/rbacService.go
+++ b/controller/rbac/rbacService.go
@@ -75,7 +75,7 @@ func (s *UserService) GetAll() ([]rbac.User, error) {
 }
 
 func (s *UserService) createUserFromStore(loadedUser rbac.LoadedUser) rbac.User {
-	return NewUser(uuid.MustParse(loadedUser.ID), loadedUser.UserName, loadedUser.Roles, loadedUser.Password, loadedUser.Token)
+	return NewUser(uuid.MustParse(loadedUser.ID), loadedUser.UserName, loadedUser.Roles, loadedUser.Password, loadedUser.Token, loadedUser.Salt)
 }
 
 //RoleService provides a role service implementation.
diff --git a/controller/rbac/user.go b/controller/rbac/user.go
index 0ff1ecd3c..52514adc5 100644
--- a/controller/rbac/user.go
+++ b/controller/rbac/user.go
@@ -15,6 +15,7 @@ type User struct {
 	Roles    map[string]string `json:"roles,omitempty"`
 	Password string            `json:"password"`
 	Token    string            `json:"token,omitempty"`
+	Salt     string            `json:"salt"`
 }
 
 // NewUser creates a new user.
@@ -22,13 +23,15 @@ func NewUser(id uuid.UUID,
 	name string,
 	roles map[string]string,
 	pw string,
-	token string) rbac.User {
+	token string,
+	salt string) rbac.User {
 	return &User{
 		UserID:   id,
 		UserName: name,
 		Roles:    roles,
 		Password: pw,
 		Token:    token,
+		Salt:     salt,
 	}
 }
 
@@ -42,11 +45,6 @@ func (u *User) Name() string {
 	return u.UserName
 }
 
-// IsCorrectPassword compares the provided with the stored password of a user.
-func (u *User) IsCorrectPassword(pwd string) bool {
-	return pwd == u.Password
-}
-
 // GetPassword returns the password of the user.
 func (u *User) GetPassword() string {
 	return u.Password
@@ -72,6 +70,11 @@ func (u *User) SetToken(token string) {
 	u.Token = token
 }
 
+// GetSalt returns the salt of the user.
+func (u *User) GetSalt() string {
+	return u.Salt
+}
+
 // MarshalJSON implements the MarshalJSON interface to store a user as JSON
 func (u *User) MarshalJSON() ([]byte, error) {
 	return json.Marshal(&struct {
@@ -80,12 +83,14 @@ func (u *User) MarshalJSON() ([]byte, error) {
 		Roles    map[string]string `json:"roles,omitempty"`
 		Password string            `json:"password"`
 		Token    string            `json:"token,omitempty"`
+		Salt     string            `json:"salt"`
 	}{
 		UserID:   u.ID(),
 		UserName: u.Name(),
 		Roles:    u.Roles,
 		Password: u.Password,
 		Token:    u.Token,
+		Salt:     u.Salt,
 	})
 }
 
@@ -97,11 +102,13 @@ func (u *User) MarshalBSON() ([]byte, error) {
 		Roles    map[string]string `bson:"roles,omitempty"`
 		Password string            `bson:"password"`
 		Token    string            `bson:"token,omitempty"`
+		Salt     string            `bson:"salt"`
 	}{
 		UserID:   u.ID().String(),
 		UserName: u.Name(),
 		Roles:    u.Roles,
 		Password: u.Password,
 		Token:    u.Token,
+		Salt:     u.Salt,
 	})
 }
-- 
GitLab