diff --git a/cmd/admin_user_create.go b/cmd/admin_user_create.go
index bf8cbc7c4c..37853cd312 100644
--- a/cmd/admin_user_create.go
+++ b/cmd/admin_user_create.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"strings"
 
 	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
@@ -61,6 +62,16 @@ var microcmdUserCreate = &cli.Command{
 			Name:  "access-token",
 			Usage: "Generate access token for the user",
 		},
+		&cli.StringFlag{
+			Name:  "access-token-name",
+			Usage: `Name of the generated access token`,
+			Value: "gitea-admin",
+		},
+		&cli.StringFlag{
+			Name:  "access-token-scopes",
+			Usage: `Scopes of the generated access token, comma separated. Examples: "all", "public-only,read:issue", "write:repository,write:user"`,
+			Value: "all",
+		},
 		&cli.BoolFlag{
 			Name:  "restricted",
 			Usage: "Make a restricted user account",
@@ -162,23 +173,39 @@ func runCreateUser(c *cli.Context) error {
 		IsRestricted: restricted,
 	}
 
+	var accessTokenName string
+	var accessTokenScope auth_model.AccessTokenScope
+	if c.IsSet("access-token") {
+		accessTokenName = strings.TrimSpace(c.String("access-token-name"))
+		if accessTokenName == "" {
+			return errors.New("access-token-name cannot be empty")
+		}
+		var err error
+		accessTokenScope, err = auth_model.AccessTokenScope(c.String("access-token-scopes")).Normalize()
+		if err != nil {
+			return fmt.Errorf("invalid access token scope provided: %w", err)
+		}
+		if !accessTokenScope.HasPermissionScope() {
+			return errors.New("access token does not have any permission")
+		}
+	} else if c.IsSet("access-token-name") || c.IsSet("access-token-scopes") {
+		return errors.New("access-token-name and access-token-scopes flags are only valid when access-token flag is set")
+	}
+
+	// arguments should be prepared before creating the user & access token, in case there is anything wrong
+
+	// create the user
 	if err := user_model.CreateUser(ctx, u, &user_model.Meta{}, overwriteDefault); err != nil {
 		return fmt.Errorf("CreateUser: %w", err)
 	}
-
-	if c.Bool("access-token") {
-		t := &auth_model.AccessToken{
-			Name: "gitea-admin",
-			UID:  u.ID,
-		}
-
+	// create the access token
+	if accessTokenScope != "" {
+		t := &auth_model.AccessToken{Name: accessTokenName, UID: u.ID, Scope: accessTokenScope}
 		if err := auth_model.NewAccessToken(ctx, t); err != nil {
 			return err
 		}
-
 		fmt.Printf("Access token was successfully created... %s\n", t.Token)
 	}
-
 	fmt.Printf("New user '%s' has been successfully created!\n", username)
 	return nil
 }
diff --git a/cmd/admin_user_create_test.go b/cmd/admin_user_create_test.go
index 83754e97b1..9f109b888e 100644
--- a/cmd/admin_user_create_test.go
+++ b/cmd/admin_user_create_test.go
@@ -8,37 +8,97 @@ import (
 	"strings"
 	"testing"
 
+	auth_model "code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/models/db"
 	"code.gitea.io/gitea/models/unittest"
 	user_model "code.gitea.io/gitea/models/user"
 
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func TestAdminUserCreate(t *testing.T) {
 	app := NewMainApp(AppVersion{})
 
 	reset := func() {
-		assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
-		assert.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
+		require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.User{}))
+		require.NoError(t, db.TruncateBeans(db.DefaultContext, &user_model.EmailAddress{}))
+		require.NoError(t, db.TruncateBeans(db.DefaultContext, &auth_model.AccessToken{}))
+	}
+	t.Run("MustChangePassword", func(t *testing.T) {
+		type check struct{ IsAdmin, MustChangePassword bool }
+		createCheck := func(name, args string) check {
+			assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
+			u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
+			return check{u.IsAdmin, u.MustChangePassword}
+		}
+		reset()
+		assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u", ""), "first non-admin user doesn't need to change password")
+
+		reset()
+		assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u", "--admin"), "first admin user doesn't need to change password")
+
+		reset()
+		assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u", "--admin --must-change-password"))
+		assert.Equal(t, check{IsAdmin: true, MustChangePassword: true}, createCheck("u2", "--admin"))
+		assert.Equal(t, check{IsAdmin: true, MustChangePassword: false}, createCheck("u3", "--admin --must-change-password=false"))
+		assert.Equal(t, check{IsAdmin: false, MustChangePassword: true}, createCheck("u4", ""))
+		assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
+	})
+
+	createUser := func(name, args string) error {
+		return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args)))
 	}
 
-	type createCheck struct{ IsAdmin, MustChangePassword bool }
-	createUser := func(name, args string) createCheck {
-		assert.NoError(t, app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s --password foobar", name, name, args))))
-		u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: name})
-		return createCheck{u.IsAdmin, u.MustChangePassword}
-	}
-	reset()
-	assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u", ""), "first non-admin user doesn't need to change password")
+	t.Run("AccessToken", func(t *testing.T) {
+		// no generated access token
+		reset()
+		assert.NoError(t, createUser("u", "--random-password"))
+		assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
+		assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
 
-	reset()
-	assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u", "--admin"), "first admin user doesn't need to change password")
+		// using "--access-token" only means "all" access
+		reset()
+		assert.NoError(t, createUser("u", "--random-password --access-token"))
+		assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
+		assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
+		accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
+		hasScopes, err := accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
+		assert.NoError(t, err)
+		assert.True(t, hasScopes)
 
-	reset()
-	assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u", "--admin --must-change-password"))
-	assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: true}, createUser("u2", "--admin"))
-	assert.Equal(t, createCheck{IsAdmin: true, MustChangePassword: false}, createUser("u3", "--admin --must-change-password=false"))
-	assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: true}, createUser("u4", ""))
-	assert.Equal(t, createCheck{IsAdmin: false, MustChangePassword: false}, createUser("u5", "--must-change-password=false"))
+		// using "--access-token" with name & scopes
+		reset()
+		assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user"))
+		assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
+		assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
+		accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
+		hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadUser)
+		assert.NoError(t, err)
+		assert.True(t, hasScopes)
+		hasScopes, err = accessToken.Scope.HasScope(auth_model.AccessTokenScopeWriteAdmin, auth_model.AccessTokenScopeWriteRepository)
+		assert.NoError(t, err)
+		assert.False(t, hasScopes)
+
+		// using "--access-token-name" without "--access-token"
+		reset()
+		err = createUser("u", "--random-password --access-token-name new-token-name")
+		assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
+		assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
+		assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
+
+		// using "--access-token-scopes" without "--access-token"
+		reset()
+		err = createUser("u", "--random-password --access-token-scopes read:issue")
+		assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
+		assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
+		assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
+
+		// empty permission
+		reset()
+		err = createUser("u", "--random-password --access-token --access-token-scopes public-only")
+		assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
+		assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
+		assert.ErrorContains(t, err, "access token does not have any permission")
+	})
 }
diff --git a/cmd/admin_user_generate_access_token.go b/cmd/admin_user_generate_access_token.go
index 6c2c10494e..f6db7a74bd 100644
--- a/cmd/admin_user_generate_access_token.go
+++ b/cmd/admin_user_generate_access_token.go
@@ -34,8 +34,8 @@ var microcmdUserGenerateAccessToken = &cli.Command{
 		},
 		&cli.StringFlag{
 			Name:  "scopes",
-			Value: "",
-			Usage: "Comma separated list of scopes to apply to access token",
+			Value: "all",
+			Usage: `Comma separated list of scopes to apply to access token, examples: "all", "public-only,read:issue", "write:repository,write:user"`,
 		},
 	},
 	Action: runGenerateAccessToken,
@@ -43,7 +43,7 @@ var microcmdUserGenerateAccessToken = &cli.Command{
 
 func runGenerateAccessToken(c *cli.Context) error {
 	if !c.IsSet("username") {
-		return errors.New("You must provide a username to generate a token for")
+		return errors.New("you must provide a username to generate a token for")
 	}
 
 	ctx, cancel := installSignals()
@@ -77,6 +77,9 @@ func runGenerateAccessToken(c *cli.Context) error {
 	if err != nil {
 		return fmt.Errorf("invalid access token scope provided: %w", err)
 	}
+	if !accessTokenScope.HasPermissionScope() {
+		return errors.New("access token does not have any permission")
+	}
 	t.Scope = accessTokenScope
 
 	// create the token
diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go
index 897ff3fc9e..aa1cc5b9de 100644
--- a/models/auth/access_token_scope.go
+++ b/models/auth/access_token_scope.go
@@ -283,6 +283,10 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
 	return bitmap.toScope(), nil
 }
 
+func (s AccessTokenScope) HasPermissionScope() bool {
+	return s != "" && s != AccessTokenScopePublicOnly
+}
+
 // PublicOnly checks if this token scope is limited to public resources
 func (s AccessTokenScope) PublicOnly() (bool, error) {
 	bitmap, err := s.parse()