diff --git a/internal/handlers/handler_register_u2f_step1_test.go b/internal/handlers/handler_register_u2f_step1_test.go
index 1706eada..b9c7c81b 100644
--- a/internal/handlers/handler_register_u2f_step1_test.go
+++ b/internal/handlers/handler_register_u2f_step1_test.go
@@ -11,8 +11,8 @@ import (
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
 
-	"github.com/authelia/authelia/v4/internal/middlewares"
 	"github.com/authelia/authelia/v4/internal/mocks"
+	"github.com/authelia/authelia/v4/internal/models"
 )
 
 type HandlerRegisterU2FStep1Suite struct {
@@ -34,34 +34,30 @@ func (s *HandlerRegisterU2FStep1Suite) TearDownTest() {
 	s.mock.Close()
 }
 
-func createToken(secret string, username string, action string, expiresAt time.Time) string {
-	claims := &middlewares.IdentityVerificationClaim{
-		RegisteredClaims: jwt.RegisteredClaims{
-			ExpiresAt: &jwt.NumericDate{
-				Time: expiresAt,
-			},
-			Issuer: "Authelia",
-		},
-		Action:   action,
-		Username: username,
-	}
+func createToken(secret, username, action string, expiresAt time.Time) (data string, verification models.IdentityVerification) {
+	verification = models.NewIdentityVerification(username, action)
+
+	verification.ExpiresAt = expiresAt
+
+	claims := verification.ToIdentityVerificationClaim()
+
 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
 	ss, _ := token.SignedString([]byte(secret))
 
-	return ss
+	return ss, verification
 }
 
 func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissing() {
-	token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", ActionU2FRegistration,
+	token, v := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", ActionU2FRegistration,
 		time.Now().Add(1*time.Minute))
 	s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
 
 	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		FindIdentityVerification(s.mock.Ctx, gomock.Eq(v.JTI.String())).
 		Return(true, nil)
 
 	s.mock.StorageProviderMock.EXPECT().
-		RemoveIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		RemoveIdentityVerification(s.mock.Ctx, gomock.Eq(v.JTI.String())).
 		Return(nil)
 
 	SecondFactorU2FIdentityFinish(s.mock.Ctx)
@@ -72,16 +68,16 @@ func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissi
 
 func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedHostIsMissing() {
 	s.mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
-	token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", ActionU2FRegistration,
+	token, v := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", ActionU2FRegistration,
 		time.Now().Add(1*time.Minute))
 	s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
 
 	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		FindIdentityVerification(s.mock.Ctx, gomock.Eq(v.JTI.String())).
 		Return(true, nil)
 
 	s.mock.StorageProviderMock.EXPECT().
-		RemoveIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		RemoveIdentityVerification(s.mock.Ctx, gomock.Eq(v.JTI.String())).
 		Return(nil)
 
 	SecondFactorU2FIdentityFinish(s.mock.Ctx)
diff --git a/internal/middlewares/const.go b/internal/middlewares/const.go
index 27c157eb..23de4427 100644
--- a/internal/middlewares/const.go
+++ b/internal/middlewares/const.go
@@ -1,7 +1,5 @@
 package middlewares
 
-const jwtIssuer = "Authelia"
-
 const (
 	headerXForwardedProto  = "X-Forwarded-Proto"
 	headerXForwardedMethod = "X-Forwarded-Method"
diff --git a/internal/middlewares/identity_verification.go b/internal/middlewares/identity_verification.go
index d4f33571..04a5bf24 100644
--- a/internal/middlewares/identity_verification.go
+++ b/internal/middlewares/identity_verification.go
@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"time"
 
 	"github.com/golang-jwt/jwt/v4"
 
@@ -20,7 +19,6 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle
 
 	return func(ctx *AutheliaCtx) {
 		identity, err := args.IdentityRetrieverFunc(ctx)
-
 		if err != nil {
 			// In that case we reply ok to avoid user enumeration.
 			ctx.Logger.Error(err)
@@ -29,17 +27,11 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle
 			return
 		}
 
+		verification := models.NewIdentityVerification(identity.Username, args.ActionClaim)
+
 		// Create the claim with the action to sign it.
-		claims := &IdentityVerificationClaim{
-			RegisteredClaims: jwt.RegisteredClaims{
-				ExpiresAt: &jwt.NumericDate{
-					Time: time.Now().Add(5 * time.Minute),
-				},
-				Issuer: jwtIssuer,
-			},
-			Action:   args.ActionClaim,
-			Username: identity.Username,
-		}
+		claims := verification.ToIdentityVerificationClaim()
+
 		token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
 		ss, err := token.SignedString([]byte(ctx.Configuration.JWTSecret))
 
@@ -48,9 +40,7 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle
 			return
 		}
 
-		err = ctx.Providers.StorageProvider.SaveIdentityVerification(ctx, models.IdentityVerification{
-			Token: ss,
-		})
+		err = ctx.Providers.StorageProvider.SaveIdentityVerification(ctx, verification)
 		if err != nil {
 			ctx.Error(err, messageOperationFailed)
 			return
@@ -131,20 +121,7 @@ func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(c
 			return
 		}
 
-		found, err := ctx.Providers.StorageProvider.FindIdentityVerification(ctx, finishBody.Token)
-
-		if err != nil {
-			ctx.Error(err, messageOperationFailed)
-			return
-		}
-
-		if !found {
-			ctx.Error(fmt.Errorf("Token is not in DB, it might have already been used"),
-				messageIdentityVerificationTokenAlreadyUsed)
-			return
-		}
-
-		token, err := jwt.ParseWithClaims(finishBody.Token, &IdentityVerificationClaim{},
+		token, err := jwt.ParseWithClaims(finishBody.Token, &models.IdentityVerificationClaim{},
 			func(token *jwt.Token) (interface{}, error) {
 				return []byte(ctx.Configuration.JWTSecret), nil
 			})
@@ -170,12 +147,31 @@ func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(c
 			return
 		}
 
-		claims, ok := token.Claims.(*IdentityVerificationClaim)
+		claims, ok := token.Claims.(*models.IdentityVerificationClaim)
 		if !ok {
 			ctx.Error(fmt.Errorf("Wrong type of claims (%T != *middlewares.IdentityVerificationClaim)", claims), messageOperationFailed)
 			return
 		}
 
+		verification, err := claims.ToIdentityVerification()
+		if err != nil {
+			ctx.Error(fmt.Errorf("Token seems to be invalid: %w", err),
+				messageOperationFailed)
+			return
+		}
+
+		found, err := ctx.Providers.StorageProvider.FindIdentityVerification(ctx, verification.JTI.String())
+		if err != nil {
+			ctx.Error(err, messageOperationFailed)
+			return
+		}
+
+		if !found {
+			ctx.Error(fmt.Errorf("Token is not in DB, it might have already been used"),
+				messageIdentityVerificationTokenAlreadyUsed)
+			return
+		}
+
 		// Verify that the action claim in the token is the one expected for the given endpoint.
 		if claims.Action != args.ActionClaim {
 			ctx.Error(fmt.Errorf("This token has not been generated for this kind of action"), messageOperationFailed)
@@ -187,8 +183,7 @@ func IdentityVerificationFinish(args IdentityVerificationFinishArgs, next func(c
 			return
 		}
 
-		// TODO(c.michaud): find a way to garbage collect unused tokens.
-		err = ctx.Providers.StorageProvider.RemoveIdentityVerification(ctx, finishBody.Token)
+		err = ctx.Providers.StorageProvider.RemoveIdentityVerification(ctx, claims.ID)
 		if err != nil {
 			ctx.Error(err, messageOperationFailed)
 			return
diff --git a/internal/middlewares/identity_verification_test.go b/internal/middlewares/identity_verification_test.go
index 29423ae6..82af45d2 100644
--- a/internal/middlewares/identity_verification_test.go
+++ b/internal/middlewares/identity_verification_test.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/authelia/authelia/v4/internal/middlewares"
 	"github.com/authelia/authelia/v4/internal/mocks"
+	"github.com/authelia/authelia/v4/internal/models"
 	"github.com/authelia/authelia/v4/internal/session"
 )
 
@@ -164,21 +165,17 @@ func (s *IdentityVerificationFinishProcess) TearDownTest() {
 	s.mock.Close()
 }
 
-func createToken(secret string, username string, action string, expiresAt time.Time) string {
-	claims := &middlewares.IdentityVerificationClaim{
-		RegisteredClaims: jwt.RegisteredClaims{
-			ExpiresAt: &jwt.NumericDate{
-				Time: expiresAt,
-			},
-			Issuer: "Authelia",
-		},
-		Action:   action,
-		Username: username,
-	}
+func createToken(secret, username, action string, expiresAt time.Time) (data string, verification models.IdentityVerification) {
+	verification = models.NewIdentityVerification(username, action)
+
+	verification.ExpiresAt = expiresAt
+
+	claims := verification.ToIdentityVerificationClaim()
+
 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
 	ss, _ := token.SignedString([]byte(secret))
 
-	return ss
+	return ss, verification
 }
 
 func next(ctx *middlewares.AutheliaCtx, username string) {}
@@ -206,10 +203,13 @@ func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenIsNotProvided()
 }
 
 func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenIsNotFoundInDB() {
-	s.mock.Ctx.Request.SetBodyString("{\"token\":\"abc\"}")
+	token, verification := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", "Login",
+		time.Now().Add(1*time.Minute))
+
+	s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
 
 	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq("abc")).
+		FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
 		Return(false, nil)
 
 	middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
@@ -221,10 +221,6 @@ func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenIsNotFoundInDB(
 func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenIsInvalid() {
 	s.mock.Ctx.Request.SetBodyString("{\"token\":\"abc\"}")
 
-	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq("abc")).
-		Return(true, nil)
-
 	middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
 
 	s.mock.Assert200KO(s.T(), "Operation failed")
@@ -233,14 +229,10 @@ func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenIsInvalid() {
 
 func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenExpired() {
 	args := newArgs(defaultRetriever)
-	token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", args.ActionClaim,
+	token, _ := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", args.ActionClaim,
 		time.Now().Add(-1*time.Minute))
 	s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
 
-	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
-		Return(true, nil)
-
 	middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
 
 	s.mock.Assert200KO(s.T(), "The identity verification token has expired")
@@ -248,12 +240,12 @@ func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenExpired() {
 }
 
 func (s *IdentityVerificationFinishProcess) TestShouldFailForWrongAction() {
-	token := createToken(s.mock.Ctx.Configuration.JWTSecret, "", "",
+	token, verification := createToken(s.mock.Ctx.Configuration.JWTSecret, "", "",
 		time.Now().Add(1*time.Minute))
 	s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
 
 	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
 		Return(true, nil)
 
 	middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
@@ -263,12 +255,12 @@ func (s *IdentityVerificationFinishProcess) TestShouldFailForWrongAction() {
 }
 
 func (s *IdentityVerificationFinishProcess) TestShouldFailForWrongUser() {
-	token := createToken(s.mock.Ctx.Configuration.JWTSecret, "harry", "EXP_ACTION",
+	token, verification := createToken(s.mock.Ctx.Configuration.JWTSecret, "harry", "EXP_ACTION",
 		time.Now().Add(1*time.Minute))
 	s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
 
 	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
 		Return(true, nil)
 
 	args := newFinishArgs()
@@ -280,16 +272,16 @@ func (s *IdentityVerificationFinishProcess) TestShouldFailForWrongUser() {
 }
 
 func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenCannotBeRemovedFromDB() {
-	token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", "EXP_ACTION",
+	token, verification := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", "EXP_ACTION",
 		time.Now().Add(1*time.Minute))
 	s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
 
 	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
 		Return(true, nil)
 
 	s.mock.StorageProviderMock.EXPECT().
-		RemoveIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		RemoveIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
 		Return(fmt.Errorf("cannot remove"))
 
 	middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
@@ -299,16 +291,16 @@ func (s *IdentityVerificationFinishProcess) TestShouldFailIfTokenCannotBeRemoved
 }
 
 func (s *IdentityVerificationFinishProcess) TestShouldReturn200OnFinishComplete() {
-	token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", "EXP_ACTION",
+	token, verification := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", "EXP_ACTION",
 		time.Now().Add(1*time.Minute))
 	s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
 
 	s.mock.StorageProviderMock.EXPECT().
-		FindIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
 		Return(true, nil)
 
 	s.mock.StorageProviderMock.EXPECT().
-		RemoveIdentityVerification(s.mock.Ctx, gomock.Eq(token)).
+		RemoveIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
 		Return(nil)
 
 	middlewares.IdentityVerificationFinish(newFinishArgs(), next)(s.mock.Ctx)
diff --git a/internal/middlewares/types.go b/internal/middlewares/types.go
index 82e3e85c..498e62fd 100644
--- a/internal/middlewares/types.go
+++ b/internal/middlewares/types.go
@@ -1,7 +1,6 @@
 package middlewares
 
 import (
-	"github.com/golang-jwt/jwt/v4"
 	"github.com/sirupsen/logrus"
 	"github.com/valyala/fasthttp"
 
@@ -80,17 +79,6 @@ type IdentityVerificationFinishArgs struct {
 	IsTokenUserValidFunc func(ctx *AutheliaCtx, username string) bool
 }
 
-// IdentityVerificationClaim custom claim for specifying the action claim.
-// The action can be to register a TOTP device, a U2F device or reset one's password.
-type IdentityVerificationClaim struct {
-	jwt.RegisteredClaims
-
-	// The action this token has been crafted for.
-	Action string `json:"action"`
-	// The user this token has been crafted for.
-	Username string `json:"username"`
-}
-
 // IdentityVerificationFinishBody type of the body received by the finish endpoint.
 type IdentityVerificationFinishBody struct {
 	Token string `json:"token"`
diff --git a/internal/models/model_identity_verification.go b/internal/models/model_identity_verification.go
index 873ba92a..e5e180c1 100644
--- a/internal/models/model_identity_verification.go
+++ b/internal/models/model_identity_verification.go
@@ -2,11 +2,69 @@ package models
 
 import (
 	"time"
+
+	"github.com/golang-jwt/jwt/v4"
+	"github.com/google/uuid"
 )
 
+// NewIdentityVerification creates a new IdentityVerification from a given username and action.
+func NewIdentityVerification(username, action string) (verification IdentityVerification) {
+	return IdentityVerification{
+		JTI:       uuid.New(),
+		IssuedAt:  time.Now(),
+		ExpiresAt: time.Now().Add(5 * time.Minute),
+		Action:    action,
+		Username:  username,
+	}
+}
+
 // IdentityVerification represents an identity verification row in the database.
 type IdentityVerification struct {
-	ID      int       `db:"id"`
-	Created time.Time `db:"created"`
-	Token   string    `db:"token"`
+	ID        int        `db:"id"`
+	JTI       uuid.UUID  `db:"jti"`
+	IssuedAt  time.Time  `db:"iat"`
+	ExpiresAt time.Time  `db:"exp"`
+	Used      *time.Time `db:"used"`
+	Action    string     `db:"action"`
+	Username  string     `db:"username"`
+}
+
+// ToIdentityVerificationClaim converts the IdentityVerification into a IdentityVerificationClaim.
+func (v IdentityVerification) ToIdentityVerificationClaim() (claim *IdentityVerificationClaim) {
+	return &IdentityVerificationClaim{
+		RegisteredClaims: jwt.RegisteredClaims{
+			ID:        v.JTI.String(),
+			Issuer:    "Authelia",
+			IssuedAt:  jwt.NewNumericDate(v.IssuedAt),
+			ExpiresAt: jwt.NewNumericDate(v.ExpiresAt),
+		},
+		Action:   v.Action,
+		Username: v.Username,
+	}
+}
+
+// IdentityVerificationClaim custom claim for specifying the action claim.
+// The action can be to register a TOTP device, a U2F device or reset one's password.
+type IdentityVerificationClaim struct {
+	jwt.RegisteredClaims
+
+	// The action this token has been crafted for.
+	Action string `json:"action"`
+	// The user this token has been crafted for.
+	Username string `json:"username"`
+}
+
+// ToIdentityVerification converts the IdentityVerificationClaim into a IdentityVerification.
+func (v IdentityVerificationClaim) ToIdentityVerification() (verification *IdentityVerification, err error) {
+	jti, err := uuid.Parse(v.ID)
+	if err != nil {
+		return nil, err
+	}
+
+	return &IdentityVerification{
+		JTI:       jti,
+		Username:  v.Username,
+		Action:    v.Action,
+		ExpiresAt: v.ExpiresAt.Time,
+	}, nil
 }
diff --git a/internal/storage/const.go b/internal/storage/const.go
index ad2e2ed2..5472b173 100644
--- a/internal/storage/const.go
+++ b/internal/storage/const.go
@@ -6,7 +6,7 @@ import (
 
 const (
 	tableUserPreferences      = "user_preferences"
-	tableIdentityVerification = "identity_verification_tokens"
+	tableIdentityVerification = "identity_verification"
 	tableTOTPConfigurations   = "totp_configurations"
 	tableU2FDevices           = "u2f_devices"
 	tableDUODevices           = "duo_devices"
diff --git a/internal/storage/migrations/V0001.Initial_Schema.all.down.sql b/internal/storage/migrations/V0001.Initial_Schema.all.down.sql
index fdee524c..0209622f 100644
--- a/internal/storage/migrations/V0001.Initial_Schema.all.down.sql
+++ b/internal/storage/migrations/V0001.Initial_Schema.all.down.sql
@@ -1,5 +1,5 @@
 DROP TABLE IF EXISTS authentication_logs;
-DROP TABLE IF EXISTS identity_verification_tokens;
+DROP TABLE IF EXISTS identity_verification;
 DROP TABLE IF EXISTS totp_configurations;
 DROP TABLE IF EXISTS u2f_devices;
 DROP TABLE IF EXISTS user_preferences;
diff --git a/internal/storage/migrations/V0001.Initial_Schema.mysql.up.sql b/internal/storage/migrations/V0001.Initial_Schema.mysql.up.sql
index 98cb08c5..e36a12b1 100644
--- a/internal/storage/migrations/V0001.Initial_Schema.mysql.up.sql
+++ b/internal/storage/migrations/V0001.Initial_Schema.mysql.up.sql
@@ -14,12 +14,16 @@ CREATE TABLE IF NOT EXISTS authentication_logs (
 CREATE INDEX authentication_logs_username_idx ON authentication_logs (time, username, auth_type);
 CREATE INDEX authentication_logs_remote_ip_idx ON authentication_logs (time, remote_ip, auth_type);
 
-CREATE TABLE IF NOT EXISTS identity_verification_tokens (
+CREATE TABLE IF NOT EXISTS identity_verification (
     id INTEGER AUTO_INCREMENT,
-    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    token VARCHAR(512),
+    jti CHAR(36),
+    iat TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    exp TIMESTAMP NOT NULL,
+    used TIMESTAMP NULL DEFAULT NULL,
+    username VARCHAR(100) NOT NULL,
+    action VARCHAR(50) NOT NULL,
     PRIMARY KEY (id),
-    UNIQUE KEY (token)
+    UNIQUE KEY (jti)
 );
 
 CREATE TABLE IF NOT EXISTS totp_configurations (
diff --git a/internal/storage/migrations/V0001.Initial_Schema.postgres.up.sql b/internal/storage/migrations/V0001.Initial_Schema.postgres.up.sql
index bef73a3f..ec7f225d 100644
--- a/internal/storage/migrations/V0001.Initial_Schema.postgres.up.sql
+++ b/internal/storage/migrations/V0001.Initial_Schema.postgres.up.sql
@@ -14,12 +14,16 @@ CREATE TABLE IF NOT EXISTS authentication_logs (
 CREATE INDEX authentication_logs_username_idx ON authentication_logs (time, username, auth_type);
 CREATE INDEX authentication_logs_remote_ip_idx ON authentication_logs (time, remote_ip, auth_type);
 
-CREATE TABLE IF NOT EXISTS identity_verification_tokens (
+CREATE TABLE IF NOT EXISTS identity_verification (
     id SERIAL,
-    created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    token VARCHAR(512),
+    jti CHAR(36),
+    iat TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    exp TIMESTAMP WITH TIME ZONE NOT NULL,
+    used TIMESTAMP WITH TIME ZONE NULL DEFAULT NULL,
+    username VARCHAR(100) NOT NULL,
+    action VARCHAR(50) NOT NULL,
     PRIMARY KEY (id),
-    UNIQUE (token)
+    UNIQUE (jti)
 );
 
 CREATE TABLE IF NOT EXISTS totp_configurations (
diff --git a/internal/storage/migrations/V0001.Initial_Schema.sqlite.up.sql b/internal/storage/migrations/V0001.Initial_Schema.sqlite.up.sql
index 06b3303e..2e91d6f9 100644
--- a/internal/storage/migrations/V0001.Initial_Schema.sqlite.up.sql
+++ b/internal/storage/migrations/V0001.Initial_Schema.sqlite.up.sql
@@ -14,12 +14,16 @@ CREATE TABLE IF NOT EXISTS authentication_logs (
 CREATE INDEX authentication_logs_username_idx ON authentication_logs (time, username, auth_type);
 CREATE INDEX authentication_logs_remote_ip_idx ON authentication_logs (time, remote_ip, auth_type);
 
-CREATE TABLE IF NOT EXISTS identity_verification_tokens (
+CREATE TABLE IF NOT EXISTS identity_verification (
     id INTEGER,
-    created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-    token VARCHAR(512),
+    jti VARCHAR(36),
+    iat TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    exp TIMESTAMP NOT NULL,
+    used TIMESTAMP NULL DEFAULT NULL,
+    username VARCHAR(100) NOT NULL,
+    action VARCHAR(50) NOT NULL,
     PRIMARY KEY (id),
-    UNIQUE (token)
+    UNIQUE (jti)
 );
 
 CREATE TABLE IF NOT EXISTS totp_configurations (
diff --git a/internal/storage/sql_provider.go b/internal/storage/sql_provider.go
index 5b876f4c..dfa23e66 100644
--- a/internal/storage/sql_provider.go
+++ b/internal/storage/sql_provider.go
@@ -81,7 +81,7 @@ type SQLProvider struct {
 	sqlInsertAuthenticationAttempt            string
 	sqlSelectAuthenticationAttemptsByUsername string
 
-	// Table: identity_verification_tokens.
+	// Table: identity_verification.
 	sqlInsertIdentityVerification       string
 	sqlDeleteIdentityVerification       string
 	sqlSelectExistsIdentityVerification string
@@ -208,7 +208,9 @@ func (p *SQLProvider) LoadUserInfo(ctx context.Context, username string) (info m
 
 // SaveIdentityVerification save an identity verification record to the database.
 func (p *SQLProvider) SaveIdentityVerification(ctx context.Context, verification models.IdentityVerification) (err error) {
-	if _, err = p.db.ExecContext(ctx, p.sqlInsertIdentityVerification, verification.Token); err != nil {
+	if _, err = p.db.ExecContext(ctx, p.sqlInsertIdentityVerification,
+		verification.JTI, verification.IssuedAt, verification.ExpiresAt,
+		verification.Username, verification.Action); err != nil {
 		return fmt.Errorf("error inserting identity verification: %w", err)
 	}
 
@@ -216,8 +218,8 @@ func (p *SQLProvider) SaveIdentityVerification(ctx context.Context, verification
 }
 
 // RemoveIdentityVerification remove an identity verification record from the database.
-func (p *SQLProvider) RemoveIdentityVerification(ctx context.Context, token string) (err error) {
-	if _, err = p.db.ExecContext(ctx, p.sqlDeleteIdentityVerification, token); err != nil {
+func (p *SQLProvider) RemoveIdentityVerification(ctx context.Context, jti string) (err error) {
+	if _, err = p.db.ExecContext(ctx, p.sqlDeleteIdentityVerification, jti); err != nil {
 		return fmt.Errorf("error updating identity verification: %w", err)
 	}
 
@@ -225,8 +227,8 @@ func (p *SQLProvider) RemoveIdentityVerification(ctx context.Context, token stri
 }
 
 // FindIdentityVerification checks if an identity verification record is in the database and active.
-func (p *SQLProvider) FindIdentityVerification(ctx context.Context, token string) (found bool, err error) {
-	if err = p.db.GetContext(ctx, &found, p.sqlSelectExistsIdentityVerification, token); err != nil {
+func (p *SQLProvider) FindIdentityVerification(ctx context.Context, jti string) (found bool, err error) {
+	if err = p.db.GetContext(ctx, &found, p.sqlSelectExistsIdentityVerification, jti); err != nil {
 		return false, fmt.Errorf("error selecting identity verification exists: %w", err)
 	}
 
diff --git a/internal/storage/sql_provider_queries.go b/internal/storage/sql_provider_queries.go
index a4cb5a1c..8f59c0c0 100644
--- a/internal/storage/sql_provider_queries.go
+++ b/internal/storage/sql_provider_queries.go
@@ -60,16 +60,17 @@ const (
 		SELECT EXISTS (
 			SELECT id
 			FROM %s
-			WHERE token = ?
+			WHERE jti = ? AND exp > CURRENT_TIMESTAMP AND used IS NULL
 		);`
 
 	queryFmtInsertIdentityVerification = `
-		INSERT INTO %s (token)
-		VALUES (?);`
+		INSERT INTO %s (jti, iat, exp, username, action)
+		VALUES (?, ?, ?, ?, ?);`
 
 	queryFmtDeleteIdentityVerification = `
-		DELETE FROM %s
-		WHERE token = ?;`
+		UPDATE %s
+		SET used = CURRENT_TIMESTAMP
+		WHERE jti = ?;`
 )
 
 const (
diff --git a/internal/suites/suite_cli_test.go b/internal/suites/suite_cli_test.go
index fae91cda..7cd7884f 100644
--- a/internal/suites/suite_cli_test.go
+++ b/internal/suites/suite_cli_test.go
@@ -261,7 +261,7 @@ func (s *CLISuite) TestStorage02ShouldShowSchemaInfo() {
 	output, err := s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info", "--config", "/config/configuration.storage.yml"})
 	s.Assert().NoError(err)
 
-	pattern := regexp.MustCompile(`^Schema Version: \d+\nSchema Upgrade Available: no\nSchema Tables: authentication_logs, identity_verification_tokens, totp_configurations, u2f_devices, user_preferences, migrations, encryption\nSchema Encryption Key: valid`)
+	pattern := regexp.MustCompile(`^Schema Version: \d+\nSchema Upgrade Available: no\nSchema Tables: authentication_logs, identity_verification, totp_configurations, u2f_devices, user_preferences, migrations, encryption\nSchema Encryption Key: valid`)
 
 	s.Assert().Regexp(pattern, output)
 }
@@ -336,7 +336,7 @@ func (s *CLISuite) TestStorage04ShouldChangeEncryptionKey() {
 	output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "schema-info", "--config", "/config/configuration.storage.yml"})
 	s.Assert().NoError(err)
 
-	pattern := regexp.MustCompile(`Schema Version: \d+\nSchema Upgrade Available: no\nSchema Tables: authentication_logs, identity_verification_tokens, totp_configurations, u2f_devices, user_preferences, migrations, encryption\nSchema Encryption Key: invalid`)
+	pattern := regexp.MustCompile(`Schema Version: \d+\nSchema Upgrade Available: no\nSchema Tables: authentication_logs, identity_verification, totp_configurations, u2f_devices, user_preferences, migrations, encryption\nSchema Encryption Key: invalid`)
 	s.Assert().Regexp(pattern, output)
 
 	output, err = s.Exec("authelia-backend", []string{"authelia", s.testArg, s.coverageArg, "storage", "encryption", "check", "--config", "/config/configuration.storage.yml"})