diff --git a/cmd/api/api.go b/cmd/api/api.go
index 18db4e07a3ba..ec729d01dc72 100644
--- a/cmd/api/api.go
+++ b/cmd/api/api.go
@@ -17,6 +17,7 @@ limitations under the License.
 package api
 
 import (
+	"bytes"
 	"context"
 	"crypto/tls"
 	"encoding/json"
@@ -26,6 +27,7 @@ import (
 	"os"
 	"path"
 	"path/filepath"
+	"slices"
 	"strings"
 	"time"
 
@@ -37,8 +39,11 @@ import (
 	"github.com/k0sproject/k0s/pkg/etcd"
 	kubeutil "github.com/k0sproject/k0s/pkg/kubernetes"
 
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
+	tokenutil "k8s.io/cluster-bootstrap/token/util"
+	bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
 
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
@@ -92,12 +97,12 @@ func (c *command) start() (err error) {
 		// Only mount the etcd handler if we're running on internal etcd storage
 		// by default the mux will return 404 back which the caller should handle
 		mux.Handle(prefix+"/etcd/members", mw.AllowMethods(http.MethodPost)(
-			c.authMiddleware(c.etcdHandler(), "usage-controller-join")))
+			c.authMiddleware(c.etcdHandler(), "controller-join")))
 	}
 
 	if storage.IsJoinable() {
 		mux.Handle(prefix+"/ca", mw.AllowMethods(http.MethodGet)(
-			c.authMiddleware(c.caHandler(), "usage-controller-join")))
+			c.authMiddleware(c.caHandler(), "controller-join")))
 	}
 
 	srv := &http.Server{
@@ -218,54 +223,54 @@ func (c *command) caHandler() http.Handler {
 // We need to validate:
 //   - that we find a secret with the ID
 //   - that the token matches whats inside the secret
-func (c *command) isValidToken(ctx context.Context, token string, usage string) bool {
-	parts := strings.Split(token, ".")
-	logrus.Debugf("token parts: %v", parts)
-	if len(parts) != 2 {
+func (c *command) isValidToken(ctx context.Context, rawTokenString string, usage string) bool {
+	tokenString, err := bootstraptokenv1.NewBootstrapTokenString(rawTokenString)
+	if err != nil {
 		return false
 	}
 
-	secretName := "bootstrap-token-" + parts[0]
+	secretName := tokenutil.BootstrapTokenSecretName(tokenString.ID)
 	secret, err := c.client.CoreV1().Secrets("kube-system").Get(ctx, secretName, metav1.GetOptions{})
 	if err != nil {
-		logrus.Errorf("failed to get bootstrap token: %s", err.Error())
+		if !apierrors.IsNotFound(err) {
+			logrus.WithError(err).Error("Failed to get bootstrap token with ID ", tokenString.ID)
+		}
 		return false
 	}
 
-	if string(secret.Data["token-secret"]) != parts[1] {
+	token, err := bootstraptokenv1.BootstrapTokenFromSecret(secret)
+	if err != nil {
+		logrus.WithError(err).Errorf("Bootstrap token with ID %s is malformed", tokenString.ID)
 		return false
 	}
 
-	usageValue, ok := secret.Data[usage]
-	if !ok || string(usageValue) != "true" {
+	if token.Expires != nil && !time.Now().Before(token.Expires.Time) {
 		return false
 	}
 
-	return true
+	if *token.Token != *tokenString {
+		return false
+	}
+
+	switch {
+	case slices.Contains(token.Usages, usage):
+		return true // usage found
+	case bytes.Equal(secret.Data["usage-"+usage], []byte("true")):
+		return true // usage found in its legacy form
+	default:
+		return false // usage not found
+	}
 }
 
 func (c *command) authMiddleware(next http.Handler, usage string) http.Handler {
 	unauthorizedErr := errors.New("go away")
 
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		auth := r.Header.Get("Authorization")
-		if auth == "" {
-			sendError(unauthorizedErr, w, http.StatusUnauthorized)
-			return
-		}
-
-		parts := strings.Split(auth, "Bearer ")
-		if len(parts) == 2 {
-			token := parts[1]
-			if !c.isValidToken(r.Context(), token, usage) {
-				sendError(unauthorizedErr, w, http.StatusUnauthorized)
-				return
-			}
+		token, ok := strings.CutPrefix(r.Header.Get("Authorization"), "Bearer ")
+		if ok && c.isValidToken(r.Context(), token, usage) {
+			next.ServeHTTP(w, r)
 		} else {
 			sendError(unauthorizedErr, w, http.StatusUnauthorized)
-			return
 		}
-
-		next.ServeHTTP(w, r)
 	})
 }
diff --git a/cmd/token/list.go b/cmd/token/list.go
index 6e9571158b03..07de8b2de875 100644
--- a/cmd/token/list.go
+++ b/cmd/token/list.go
@@ -47,7 +47,7 @@ func tokenListCmd() *cobra.Command {
 				return err
 			}
 
-			tokens, err := manager.List(cmd.Context(), listTokenRole)
+			tokens, err := manager.List(cmd.Context())
 			if err != nil {
 				return err
 			}
@@ -70,7 +70,9 @@ func tokenListCmd() *cobra.Command {
 			table.SetTablePadding("\t") // pad with tabs
 			table.SetNoWhiteSpace(true)
 			for _, t := range tokens {
-				table.Append(t.ToArray())
+				if listTokenRole == "" || listTokenRole == t.Role {
+					table.Append(t.ToArray())
+				}
 			}
 
 			table.Render()
diff --git a/cmd/token/preshared.go b/cmd/token/preshared.go
index 769481600877..231d79952aeb 100644
--- a/cmd/token/preshared.go
+++ b/cmd/token/preshared.go
@@ -29,6 +29,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/runtime/serializer/json"
 	"k8s.io/client-go/kubernetes/scheme"
+	bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
 
 	"github.com/k0sproject/k0s/internal/pkg/file"
 	"github.com/k0sproject/k0s/pkg/config"
@@ -90,10 +91,10 @@ func preSharedCmd() *cobra.Command {
 	return cmd
 }
 
-func createSecret(role string, validity time.Duration, outDir string) (string, error) {
+func createSecret(role string, validity time.Duration, outDir string) (*bootstraptokenv1.BootstrapTokenString, error) {
 	secret, token, err := token.RandomBootstrapSecret(role, validity)
 	if err != nil {
-		return "", fmt.Errorf("failed to generate bootstrap secret: %w", err)
+		return nil, fmt.Errorf("failed to generate bootstrap secret: %w", err)
 	}
 
 	if err := file.WriteAtomically(filepath.Join(outDir, secret.Name+".yaml"), 0640, func(unbuffered io.Writer) error {
@@ -105,13 +106,13 @@ func createSecret(role string, validity time.Duration, outDir string) (string, e
 		}
 		return w.Flush()
 	}); err != nil {
-		return "", fmt.Errorf("failed to save bootstrap secret: %w", err)
+		return nil, fmt.Errorf("failed to save bootstrap secret: %w", err)
 	}
 
 	return token, nil
 }
 
-func createKubeConfig(tokenString, role, joinURL, certPath, outDir string) error {
+func createKubeConfig(tok *bootstraptokenv1.BootstrapTokenString, role, joinURL, certPath, outDir string) error {
 	caCert, err := os.ReadFile(certPath)
 	if err != nil {
 		return fmt.Errorf("error reading certificate: %w", err)
@@ -126,7 +127,7 @@ func createKubeConfig(tokenString, role, joinURL, certPath, outDir string) error
 	default:
 		return fmt.Errorf("unknown role: %s", role)
 	}
-	kubeconfig, err := token.GenerateKubeconfig(joinURL, caCert, userName, tokenString)
+	kubeconfig, err := token.GenerateKubeconfig(joinURL, caCert, userName, tok)
 	if err != nil {
 		return fmt.Errorf("error generating kubeconfig: %w", err)
 	}
@@ -136,7 +137,7 @@ func createKubeConfig(tokenString, role, joinURL, certPath, outDir string) error
 		return fmt.Errorf("error encoding token: %w", err)
 	}
 
-	err = file.WriteContentAtomically(filepath.Join(outDir, "token_"+tokenString), []byte(encodedToken), 0640)
+	err = file.WriteContentAtomically(filepath.Join(outDir, "token_"+tok.ID), []byte(encodedToken), 0640)
 	if err != nil {
 		return fmt.Errorf("error writing kubeconfig: %w", err)
 	}
diff --git a/go.mod b/go.mod
index ae7cbe12c79e..36fde7132274 100644
--- a/go.mod
+++ b/go.mod
@@ -69,6 +69,7 @@ require (
 	k8s.io/cli-runtime v0.31.3
 	k8s.io/client-go v0.31.3
 	k8s.io/cloud-provider v0.31.3
+	k8s.io/cluster-bootstrap v0.31.3
 	k8s.io/component-base v0.31.3
 	k8s.io/component-helpers v0.31.3
 	k8s.io/cri-api v0.31.3
diff --git a/go.sum b/go.sum
index af98085222f6..00da6e193328 100644
--- a/go.sum
+++ b/go.sum
@@ -851,6 +851,8 @@ k8s.io/client-go v0.31.3 h1:CAlZuM+PH2cm+86LOBemaJI/lQ5linJ6UFxKX/SoG+4=
 k8s.io/client-go v0.31.3/go.mod h1:2CgjPUTpv3fE5dNygAr2NcM8nhHzXvxB8KL5gYc3kJs=
 k8s.io/cloud-provider v0.31.3 h1:7C3CHQUUwnv/HWWVIaibZH06iPg663RYQ6C6Zy4FnO8=
 k8s.io/cloud-provider v0.31.3/go.mod h1:c7csKppoVb9Ej6upJ28AvHy4B3BtlRMzXfgezsDdPKw=
+k8s.io/cluster-bootstrap v0.31.3 h1:O1Yxk1bLaxZvmQCXLaJjj5iJD+lVMfJdRUuKgbUHPlA=
+k8s.io/cluster-bootstrap v0.31.3/go.mod h1:TI6TCsQQB4FfcryWgNO3SLXSKWBqHjx4DfyqSFwixj8=
 k8s.io/component-base v0.31.3 h1:DMCXXVx546Rfvhj+3cOm2EUxhS+EyztH423j+8sOwhQ=
 k8s.io/component-base v0.31.3/go.mod h1:xME6BHfUOafRgT0rGVBGl7TuSg8Z9/deT7qq6w7qjIU=
 k8s.io/component-helpers v0.31.3 h1:0zGPD2PrekhFWgmz85XxlMEl7dfhlKC1tERZDe3onQc=
diff --git a/internal/autopilot/pkg/random/random.go b/internal/autopilot/pkg/random/random.go
deleted file mode 100644
index 861d33dfb21a..000000000000
--- a/internal/autopilot/pkg/random/random.go
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2021 k0s authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package random
-
-import (
-	"crypto/rand"
-)
-
-var letters = "abcdefghijklmnopqrstuvwxyz0123456789"
-
-// String generates a random string with given length
-func String(length int) string {
-
-	bytes := make([]byte, length)
-	if _, err := rand.Read(bytes); err != nil {
-		// Not much we can do on broken system
-		panic("random is broken: " + err.Error())
-	}
-
-	for i, b := range bytes {
-		bytes[i] = letters[b%byte(len(letters))]
-	}
-	return string(bytes)
-}
diff --git a/internal/pkg/random/random.go b/internal/pkg/random/random.go
deleted file mode 100644
index fe69bfbfcc9f..000000000000
--- a/internal/pkg/random/random.go
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
-Copyright 2021 k0s authors
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-*/
-
-package random
-
-import "crypto/rand"
-
-var letters = "abcdefghijklmnopqrstuvwxyz0123456789"
-
-// String generates a random string with given length
-func String(length int) string {
-
-	bytes := make([]byte, length)
-	if _, err := rand.Read(bytes); err != nil {
-		// Not much we can do on broken system
-		panic("random is broken: " + err.Error())
-	}
-
-	for i, b := range bytes {
-		bytes[i] = letters[b%byte(len(letters))]
-	}
-	return string(bytes)
-}
diff --git a/pkg/token/joinclient_test.go b/pkg/token/joinclient_test.go
index 0b2ef274eb9d..dbbf3133839c 100644
--- a/pkg/token/joinclient_test.go
+++ b/pkg/token/joinclient_test.go
@@ -31,6 +31,8 @@ import (
 	k0sv1beta1 "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1"
 	"github.com/k0sproject/k0s/pkg/token"
 
+	bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
+
 	"github.com/cloudflare/cfssl/csr"
 	"github.com/cloudflare/cfssl/initca"
 	"github.com/stretchr/testify/assert"
@@ -42,13 +44,13 @@ func TestJoinClient_GetCA(t *testing.T) {
 
 	joinURL, certData := startFakeJoinServer(t, func(res http.ResponseWriter, req *http.Request) {
 		assert.Equal(t, "/some/sub/path/v1beta1/ca", req.RequestURI)
-		assert.Equal(t, []string{"Bearer the-token"}, req.Header["Authorization"])
+		assert.Equal(t, []string{"Bearer the-id.the-secret"}, req.Header["Authorization"])
 		_, err := res.Write([]byte("{}"))
 		assert.NoError(t, err)
 	})
 
 	joinURL.Path = "/some/sub/path"
-	kubeconfig, err := token.GenerateKubeconfig(joinURL.String(), certData, t.Name(), "the-token")
+	kubeconfig, err := token.GenerateKubeconfig(joinURL.String(), certData, t.Name(), &bootstraptokenv1.BootstrapTokenString{ID: "the-id", Secret: "the-secret"})
 	require.NoError(t, err)
 	tok, err := token.JoinEncode(bytes.NewReader(kubeconfig))
 	require.NoError(t, err)
@@ -66,7 +68,7 @@ func TestJoinClient_JoinEtcd(t *testing.T) {
 
 	joinURL, certData := startFakeJoinServer(t, func(res http.ResponseWriter, req *http.Request) {
 		assert.Equal(t, "/some/sub/path/v1beta1/etcd/members", req.RequestURI)
-		assert.Equal(t, []string{"Bearer the-token"}, req.Header["Authorization"])
+		assert.Equal(t, []string{"Bearer the-id.the-secret"}, req.Header["Authorization"])
 
 		if body, err := io.ReadAll(req.Body); assert.NoError(t, err) {
 			var data map[string]string
@@ -83,7 +85,7 @@ func TestJoinClient_JoinEtcd(t *testing.T) {
 	})
 
 	joinURL.Path = "/some/sub/path"
-	kubeconfig, err := token.GenerateKubeconfig(joinURL.String(), certData, t.Name(), "the-token")
+	kubeconfig, err := token.GenerateKubeconfig(joinURL.String(), certData, t.Name(), &bootstraptokenv1.BootstrapTokenString{ID: "the-id", Secret: "the-secret"})
 	require.NoError(t, err)
 	tok, err := token.JoinEncode(bytes.NewReader(kubeconfig))
 	require.NoError(t, err)
@@ -124,7 +126,7 @@ func TestJoinClient_Cancellation(t *testing.T) {
 				<-req.Context().Done()              // block forever
 			})
 
-			kubeconfig, err := token.GenerateKubeconfig(joinURL.String(), certData, "", "")
+			kubeconfig, err := token.GenerateKubeconfig(joinURL.String(), certData, "", &bootstraptokenv1.BootstrapTokenString{})
 			require.NoError(t, err)
 			tok, err := token.JoinEncode(bytes.NewReader(kubeconfig))
 			require.NoError(t, err)
diff --git a/pkg/token/kubeconfig.go b/pkg/token/kubeconfig.go
index 58657340124b..6882a556c57c 100644
--- a/pkg/token/kubeconfig.go
+++ b/pkg/token/kubeconfig.go
@@ -29,6 +29,7 @@ import (
 
 	"k8s.io/client-go/tools/clientcmd"
 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
+	bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
 )
 
 const (
@@ -61,7 +62,7 @@ func CreateKubeletBootstrapToken(ctx context.Context, api *v1beta1.APISpec, k0sV
 	return JoinEncode(bytes.NewReader(kubeconfig))
 }
 
-func GenerateKubeconfig(joinURL string, caCert []byte, userName string, token string) ([]byte, error) {
+func GenerateKubeconfig(joinURL string, caCert []byte, userName string, token *bootstraptokenv1.BootstrapTokenString) ([]byte, error) {
 	const k0sContextName = "k0s"
 	kubeconfig, err := clientcmd.Write(clientcmdapi.Config{
 		Clusters: map[string]*clientcmdapi.Cluster{k0sContextName: {
@@ -74,7 +75,7 @@ func GenerateKubeconfig(joinURL string, caCert []byte, userName string, token st
 		}},
 		CurrentContext: k0sContextName,
 		AuthInfos: map[string]*clientcmdapi.AuthInfo{userName: {
-			Token: token,
+			Token: token.String(),
 		}},
 	})
 	return kubeconfig, err
@@ -101,10 +102,10 @@ func loadCACert(k0sVars *config.CfgVars) ([]byte, error) {
 	return caCert, nil
 }
 
-func loadToken(ctx context.Context, k0sVars *config.CfgVars, role string, expiry time.Duration) (string, error) {
+func loadToken(ctx context.Context, k0sVars *config.CfgVars, role string, expiry time.Duration) (*bootstraptokenv1.BootstrapTokenString, error) {
 	manager, err := NewManager(k0sVars.AdminKubeConfigPath)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 	return manager.Create(ctx, expiry, role)
 }
diff --git a/pkg/token/kubeconfig_test.go b/pkg/token/kubeconfig_test.go
index 4e67f34856ba..6cb8da87176c 100644
--- a/pkg/token/kubeconfig_test.go
+++ b/pkg/token/kubeconfig_test.go
@@ -19,6 +19,8 @@ package token
 import (
 	"testing"
 
+	bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
+
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -41,10 +43,11 @@ preferences: {}
 users:
 - name: the user
   user:
-    token: the token
+    token: abcdef.0123456789abcdef
 `
 
-	kubeconfig, err := GenerateKubeconfig("the join URL", []byte("the cert"), "the user", "the token")
+	tok := bootstraptokenv1.BootstrapTokenString{ID: "abcdef", Secret: "0123456789abcdef"}
+	kubeconfig, err := GenerateKubeconfig("the join URL", []byte("the cert"), "the user", &tok)
 	require.NoError(t, err)
 	assert.Equal(t, expected, string(kubeconfig))
 }
diff --git a/pkg/token/manager.go b/pkg/token/manager.go
index 194330040a5a..b853732bde6d 100644
--- a/pkg/token/manager.go
+++ b/pkg/token/manager.go
@@ -17,19 +17,23 @@ limitations under the License.
 package token
 
 import (
+	"bytes"
 	"context"
 	"fmt"
+	"slices"
 	"time"
 
-	"github.com/sirupsen/logrus"
+	k8sutil "github.com/k0sproject/k0s/pkg/kubernetes"
+
 	corev1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/api/errors"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/fields"
 	"k8s.io/client-go/kubernetes"
+	tokenutil "k8s.io/cluster-bootstrap/token/util"
+	bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
 
-	"github.com/k0sproject/k0s/internal/pkg/random"
-	k8sutil "github.com/k0sproject/k0s/pkg/kubernetes"
+	"github.com/sirupsen/logrus"
 )
 
 type Token struct {
@@ -66,87 +70,105 @@ type Manager struct {
 	client kubernetes.Interface
 }
 
-func RandomBootstrapSecret(role string, valid time.Duration) (*corev1.Secret, string, error) {
-	tokenID := random.String(6)
-	tokenSecret := random.String(16)
-
-	s := corev1.Secret{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "bootstrap-token-" + tokenID,
-			Namespace: "kube-system",
-		},
-		Type: corev1.SecretTypeBootstrapToken,
-		StringData: map[string]string{
-			"token-id":     tokenID,
-			"token-secret": tokenSecret,
-		},
+func RandomBootstrapSecret(role string, ttl time.Duration) (*corev1.Secret, *bootstraptokenv1.BootstrapTokenString, error) {
+	token := bootstraptokenv1.BootstrapToken{
+		TTL: &metav1.Duration{Duration: ttl},
 	}
 
-	if valid != 0 {
-		exp := time.Now().Add(valid).UTC().Format(time.RFC3339)
-		s.StringData["expiration"] = exp
-		logrus.Debug("Set expiry to ", exp)
+	var err error
+	token.Token, err = generateBootstrapToken()
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to generate bootstrap token: %w", err)
 	}
 
+	var legacyUsages []string // legacy usages for backwards compatibility
+
 	switch role {
-	case "worker":
-		s.StringData["description"] = "Worker bootstrap token generated by k0s"
-		s.StringData["usage-bootstrap-authentication"] = "true"
-	case "controller":
-		s.StringData["description"] = "Controller bootstrap token generated by k0s"
-		s.StringData["usage-controller-join"] = "true"
+	case RoleWorker:
+		token.Description = "Worker bootstrap token generated by k0s"
+		token.Usages = append(token.Usages, "authentication")
+	case RoleController:
+		token.Description = "Controller bootstrap token generated by k0s"
+		token.Usages = append(token.Usages, "controller-join")
+		legacyUsages = append(legacyUsages, "controller-join")
 	default:
-		return nil, "", fmt.Errorf("unsupported role %q", role)
+		return nil, nil, fmt.Errorf("unsupported role %q", role)
+	}
+
+	secret := bootstraptokenv1.BootstrapTokenToSecret(&token)
+	for _, usage := range legacyUsages {
+		// Add the usages also in their legacy form.
+		secret.Data["usage-"+usage] = []byte("true")
 	}
 
-	return &s, fmt.Sprintf("%s.%s", tokenID, tokenSecret), nil
+	return secret, token.Token, nil
 }
 
 // Create creates a new bootstrap token
-func (m *Manager) Create(ctx context.Context, valid time.Duration, role string) (string, error) {
+func (m *Manager) Create(ctx context.Context, valid time.Duration, role string) (*bootstraptokenv1.BootstrapTokenString, error) {
 	secret, token, err := RandomBootstrapSecret(role, valid)
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 
-	_, err = m.client.CoreV1().Secrets("kube-system").Create(ctx, secret, metav1.CreateOptions{})
+	_, err = m.client.CoreV1().Secrets(metav1.NamespaceSystem).Create(ctx, secret, metav1.CreateOptions{})
 	if err != nil {
-		return "", err
+		return nil, err
 	}
 
 	return token, nil
 }
 
-// List returns all the join tokens for given role. If role == "" then it returns all join tokens
-func (m *Manager) List(ctx context.Context, role string) ([]Token, error) {
-	tokenList, err := m.client.CoreV1().Secrets("kube-system").List(ctx, metav1.ListOptions{
+// List returns all the join tokens.
+func (m *Manager) List(ctx context.Context) (tokens []Token, _ error) {
+	secrets, err := m.client.CoreV1().Secrets(metav1.NamespaceSystem).List(ctx, metav1.ListOptions{
 		FieldSelector: fields.OneTermEqualSelector("type", string(corev1.SecretTypeBootstrapToken)).String(),
 	})
 	if err != nil {
 		return nil, err
 	}
-	tokens := make([]Token, 0, len(tokenList.Items))
 
-	for _, t := range tokenList.Items {
-		r := "worker"
-		if string(t.Data["usage-controller-join"]) == "true" {
-			r = "controller"
+	for _, secret := range secrets.Items {
+		parsed, err := bootstraptokenv1.BootstrapTokenFromSecret(&secret)
+		if err != nil {
+			continue // ignore invalid tokens
 		}
-		if r == role || role == "" {
-			tokens = append(tokens, Token{
-				ID:     string(t.Data["token-id"]),
-				Role:   r,
-				Expiry: string(t.Data["expiration"]),
-			})
+
+		token := Token{ID: parsed.Token.ID}
+
+		if slices.Contains(parsed.Usages, "controller-join") {
+			token.Role = "controller"
+		} else if bytes.Equal(secret.Data["usage-controller-join"], []byte("true")) {
+			// Legacy form of token usage
+			token.Role = "controller"
+		} else if slices.Contains(parsed.Usages, "authentication") {
+			token.Role = "worker"
+		}
+
+		if parsed.Expires != nil {
+			token.Expiry = parsed.Expires.UTC().Format(time.RFC3339)
 		}
+
+		tokens = append(tokens, token)
 	}
+
 	return tokens, nil
 }
 
 func (m *Manager) Remove(ctx context.Context, tokenID string) error {
-	err := m.client.CoreV1().Secrets("kube-system").Delete(ctx, "bootstrap-token-"+tokenID, metav1.DeleteOptions{})
-	if errors.IsNotFound(err) {
+	err := m.client.CoreV1().Secrets(metav1.NamespaceSystem).Delete(ctx, tokenutil.BootstrapTokenSecretName(tokenID), metav1.DeleteOptions{})
+	if apierrors.IsNotFound(err) {
 		return nil
 	}
 	return err
 }
+
+// Generates a new, random Bootstrap Token.
+func generateBootstrapToken() (*bootstraptokenv1.BootstrapTokenString, error) {
+	token, err := tokenutil.GenerateBootstrapToken()
+	if err != nil {
+		return nil, err
+	}
+
+	return bootstraptokenv1.NewBootstrapTokenString(token)
+}