diff --git a/auth/auth.go b/auth/auth.go
index 8f17e465eba0a4bc37efba5b4e9dc145408ec939_YXV0aC9hdXRoLmdv..866dbada1e4d3ab9e21e7b6a09187fe693f8c2b2_YXV0aC9hdXRoLmdv 100644
--- a/auth/auth.go
+++ b/auth/auth.go
@@ -1,9 +1,10 @@
 package auth
 
 import (
+	"crypto/rand"
 	"encoding/hex"
 	"errors"
 	"fmt"
 	"net/http"
 	"time"
 
@@ -4,9 +5,10 @@
 	"encoding/hex"
 	"errors"
 	"fmt"
 	"net/http"
 	"time"
 
+	"github.com/rs/zerolog"
 	"gopkg.in/dgrijalva/jwt-go.v3"
 )
 
@@ -36,7 +38,7 @@
 //
 //nolint:lll
 type TokenOptions struct {
-	SecretOpt          func(string) error `long:"token-secret" env:"AUTH_TOKEN_SECRET" ini-name:"secret" required:"t" description:"hex HMAC256/AES256 secret for signing/verifying JWT & crypting (32 bytes)"`
+	SecretOpt          string             `long:"token-secret" env:"AUTH_TOKEN_SECRET" ini-name:"secret" description:"hex HMAC256/AES256 secret for signing/verifying JWT & crypting (32 bytes)"`
 	Secret             []byte             `no-flag:"t"`
 	Expiration         time.Duration      `no-flag:"t"`
 	ExpirationOpt      func(string) error `long:"token-expiration" ini-name:"expiration" required:"false" description:"Expiration time of the generated tokens" default:"5m"`
@@ -47,6 +49,7 @@
 	CachePurgeDelayOpt func(string) error `long:"token-cache-purge-delay" ini-name:"cache-purge-delay" required:"false" description:"Delay between token cache purges" default:"10m"`
 
 	cookieBasename string
+	log            func() *zerolog.Logger
 }
 
 func durationOption(tgt *time.Duration) func(string) error {
@@ -61,19 +64,4 @@
 	}
 }
 
-func hexBytesOption(size int, tgt *[]byte) func(string) error {
-	return func(s string) error {
-		b, err := hex.DecodeString(s)
-		if err != nil {
-			return fmt.Errorf("invalid hex string: %w", err)
-		}
-		if len(b) != size {
-			return fmt.Errorf("invalid option size. Expected %d bytes, got: %d", size, len(b))
-		}
-		*tgt = b
-
-		return nil
-	}
-}
-
 // NewTokenOptions creates a TokenOptions.
@@ -79,4 +67,4 @@
 // NewTokenOptions creates a TokenOptions.
-func NewTokenOptions(cookieBasename string) *TokenOptions {
+func NewTokenOptions(cookieBasename string, getLog func() *zerolog.Logger) *TokenOptions {
 	options := TokenOptions{
 		cookieBasename: cookieBasename,
@@ -81,5 +69,6 @@
 	options := TokenOptions{
 		cookieBasename: cookieBasename,
+		log:            getLog,
 	}
 	options.ExpirationOpt = durationOption(&options.Expiration)
 	options.CachePurgeDelayOpt = durationOption(&options.CachePurgeDelay)
@@ -83,7 +72,6 @@
 	}
 	options.ExpirationOpt = durationOption(&options.Expiration)
 	options.CachePurgeDelayOpt = durationOption(&options.CachePurgeDelay)
-	options.SecretOpt = hexBytesOption(32, &options.Secret)
 
 	return &options
 }
@@ -93,6 +81,30 @@
 	SetExpiresAt(time.Time)
 }
 
-func (t TokenOptions) MakeToken(
+// EnsureSecret makes sure a valid secret is configured.
+func (t *TokenOptions) EnsureSecret() error {
+	if len(t.Secret) == 32 {
+		return nil
+	}
+	if t.SecretOpt == "" {
+		t.log().Warn().Msg("no secret configured. A random secret will be used, meaning authenticated session will not survice a server restart. To avoid this, please set the token-secret option")
+
+		t.Secret = make([]byte, 32)
+		_, _ = rand.Read(t.Secret)
+	} else {
+		b, err := hex.DecodeString(t.SecretOpt)
+		if err != nil {
+			return fmt.Errorf("'secret' is not a valid hex string: %w", err)
+		}
+		if len(b) != 32 {
+			return fmt.Errorf("'secret' has in invalid size. Expected %d bytes, got: %d", 32, len(b))
+		}
+		t.Secret = b
+	}
+
+	return nil
+}
+
+func (t *TokenOptions) MakeToken(
 	claims Claims, withCookie bool,
 ) (string, *http.Cookie, error) {
@@ -97,5 +109,8 @@
 	claims Claims, withCookie bool,
 ) (string, *http.Cookie, error) {
+	if err := t.EnsureSecret(); err != nil {
+		return "", nil, err
+	}
 	expiration := t.Expiration
 	if withCookie {
 		expiration *= 2
@@ -123,7 +138,10 @@
 	return stoken, cookie, nil
 }
 
-func (t TokenOptions) ParseToken(tokenString string, claims Claims) error {
+func (t *TokenOptions) ParseToken(tokenString string, claims Claims) error {
+	if err := t.EnsureSecret(); err != nil {
+		return err
+	}
 	token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (any, error) {
 		return t.Secret, nil
 	})
diff --git a/cmd/program.go b/cmd/program.go
index 8f17e465eba0a4bc37efba5b4e9dc145408ec939_Y21kL3Byb2dyYW0uZ28=..866dbada1e4d3ab9e21e7b6a09187fe693f8c2b2_Y21kL3Byb2dyYW0uZ28= 100644
--- a/cmd/program.go
+++ b/cmd/program.go
@@ -168,15 +168,6 @@
 	})
 }
 
-func WithTokenOptions[E any]() Option[E] {
-	return func(program *Program[E]) {
-		program.TokenOptions = auth.NewTokenOptions(program.Name)
-		if _, err := program.Parser.AddGroup("Token", "Token Options", program.TokenOptions); err != nil {
-			panic(err)
-		}
-	}
-}
-
 func WithDatabase[E any](migrateSource source.Driver) Option[E] {
 	return func(program *Program[E]) {
 		program.hasDB = true
diff --git a/cmd/program_auth.go b/cmd/program_auth.go
index 8f17e465eba0a4bc37efba5b4e9dc145408ec939_Y21kL3Byb2dyYW1fYXV0aC5nbw==..866dbada1e4d3ab9e21e7b6a09187fe693f8c2b2_Y21kL3Byb2dyYW1fYXV0aC5nbw== 100644
--- a/cmd/program_auth.go
+++ b/cmd/program_auth.go
@@ -1,4 +1,8 @@
 package cmd
 
 import (
+	"crypto/rand"
+	"encoding/hex"
+	"fmt"
+
 	"github.com/justinas/alice"
@@ -4,5 +8,6 @@
 	"github.com/justinas/alice"
+	"github.com/rs/zerolog"
 
 	"orus.io/orus-io/go-orusapi/auth"
 )
 
@@ -5,7 +10,23 @@
 
 	"orus.io/orus-io/go-orusapi/auth"
 )
 
+func WithTokenOptions[E any]() Option[E] {
+	return func(program *Program[E]) {
+		program.TokenOptions = auth.NewTokenOptions(
+			program.Name,
+			func() *zerolog.Logger {
+				return &program.Logger
+			},
+		)
+		if _, err := program.Parser.AddGroup("Token", "Token Options", program.TokenOptions); err != nil {
+			panic(err)
+		}
+
+		SetupGenerateAuthSecretCmd(program)
+	}
+}
+
 func WithAuthMiddleware[E any](middleware any) Option[E] {
 	return func(program *Program[E]) {
 		program.authMiddlewares = append(program.authMiddlewares, getMiddleware[E](middleware))
@@ -22,3 +43,32 @@
 		},
 	)
 }
+
+type GenerateAuthSecretCmd[E any] struct {
+	program *Program[E]
+}
+
+func (cmd *GenerateAuthSecretCmd[E]) Execute([]string) error {
+	secret := make([]byte, 32)
+	if _, err := rand.Read(secret); err != nil {
+		panic(err)
+	}
+
+	_, err := fmt.Println(hex.EncodeToString(secret))
+
+	return err
+}
+
+func SetupGenerateAuthSecretCmd[E any](program *Program[E]) *GenerateAuthSecretCmd[E] {
+	cmd := GenerateAuthSecretCmd[E]{
+		program: program,
+	}
+
+	if _, err := program.Parser.AddCommand(
+		"generate-auth-secret", "Generate a proper auth secret", "", &cmd,
+	); err != nil {
+		program.Logger.Fatal().Err(err).Msg("could not init generate-auth-secret command")
+	}
+
+	return &cmd
+}
diff --git a/cmd/serve.go b/cmd/serve.go
index 8f17e465eba0a4bc37efba5b4e9dc145408ec939_Y21kL3NlcnZlLmdv..866dbada1e4d3ab9e21e7b6a09187fe693f8c2b2_Y21kL3NlcnZlLmdv 100644
--- a/cmd/serve.go
+++ b/cmd/serve.go
@@ -38,6 +38,9 @@
 }
 
 func (cmd *ServeCmd[E]) Execute([]string) error {
+	if err := cmd.program.TokenOptions.EnsureSecret(); err != nil {
+		return err
+	}
 	if err := cmd.program.EnsureDB(cmd.AutoMigrate); err != nil {
 		return err
 	}