diff --git a/README.md b/README.md
index 9161dad1d6aeb8752c4b087eccc1392dc017cdb7..7247270ae6a1ca20248f654c45b141f41b930282 100644
--- a/README.md
+++ b/README.md
@@ -63,17 +63,17 @@ Depending on the connectors limitations in protocols can prevent dex from issuin
 
 Dex implements the following connectors:
 
-| Name | supports refresh tokens | supports groups claim | status | notes |
-| ---- | ----------------------- | --------------------- | ------ | ----- |
-| [LDAP](Documentation/connectors/ldap.md) | yes | yes | stable | |
-| [GitHub](Documentation/connectors/github.md) | yes | yes | stable | |
-| [SAML 2.0](Documentation/connectors/saml.md) | no | yes | stable |
-| [GitLab](Documentation/connectors/gitlab.md) | yes | yes | beta | |
-| [OpenID Connect](Documentation/connectors/oidc.md) | yes | no ([#1065][issue-1065]) | beta | Includes Google, Salesforce, Azure, etc. |
-| [LinkedIn](Documentation/connectors/linkedin.md) | yes | no | beta | |
-| [Microsoft](Documentation/connectors/microsoft.md) | yes | yes | beta | |
-| [AuthProxy](Documentation/connectors/authproxy.md) | no | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. |
-| [Bitbucket Cloud](Documentation/connectors/bitbucketcloud.md) | yes | yes | alpha | |
+| Name | supports refresh tokens | supports groups claim | supports preferred_username claim | status | notes |
+| ---- | ----------------------- | --------------------- | --------------------------------- | ------ | ----- |
+| [LDAP](Documentation/connectors/ldap.md) | yes | yes | yes | stable | |
+| [GitHub](Documentation/connectors/github.md) | yes | yes | yes | stable | |
+| [SAML 2.0](Documentation/connectors/saml.md) | no | yes | no | stable |
+| [GitLab](Documentation/connectors/gitlab.md) | yes | yes | yes | beta | |
+| [OpenID Connect](Documentation/connectors/oidc.md) | yes | no ([#1065][issue-1065]) | no | beta | Includes Google, Salesforce, Azure, etc. |
+| [LinkedIn](Documentation/connectors/linkedin.md) | yes | no | no | beta | |
+| [Microsoft](Documentation/connectors/microsoft.md) | yes | yes | no | beta | |
+| [AuthProxy](Documentation/connectors/authproxy.md) | no | no | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. |
+| [Bitbucket Cloud](Documentation/connectors/bitbucketcloud.md) | yes | yes | no | alpha | |
 
 Stable, beta, and alpha are defined as:
 
diff --git a/connector/connector.go b/connector/connector.go
index edd7fa57063fca79c40fc84033cf03fd9ad0d038..aab994b4682a7529cee369a5ef04f4ba042777cb 100644
--- a/connector/connector.go
+++ b/connector/connector.go
@@ -23,10 +23,11 @@ type Scopes struct {
 
 // Identity represents the ID Token claims supported by the server.
 type Identity struct {
-	UserID        string
-	Username      string
-	Email         string
-	EmailVerified bool
+	UserID            string
+	Username          string
+	PreferredUsername string
+	Email             string
+	EmailVerified     bool
 
 	Groups []string
 
diff --git a/connector/github/github.go b/connector/github/github.go
index 6fc4cc030403b070406b3a8a05a7bd1f226096f8..6d915edcec7dbacb5f6bb692ee9dc7ad87162e82 100644
--- a/connector/github/github.go
+++ b/connector/github/github.go
@@ -266,10 +266,11 @@ func (c *githubConnector) HandleCallback(s connector.Scopes, r *http.Request) (i
 	}
 
 	identity = connector.Identity{
-		UserID:        strconv.Itoa(user.ID),
-		Username:      username,
-		Email:         user.Email,
-		EmailVerified: true,
+		UserID:            strconv.Itoa(user.ID),
+		Username:          username,
+		PreferredUsername: user.Login,
+		Email:             user.Email,
+		EmailVerified:     true,
 	}
 	if c.useLoginAsID {
 		identity.UserID = user.Login
@@ -317,6 +318,7 @@ func (c *githubConnector) Refresh(ctx context.Context, s connector.Scopes, ident
 		username = user.Login
 	}
 	identity.Username = username
+	identity.PreferredUsername = user.Login
 	identity.Email = user.Email
 
 	// Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified.
diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go
index d9cffdedd50f40e1b33f447aed1a4fe62a2aeb78..a9b0abe2b25574cb48e948b02ff5b2fe235729b0 100644
--- a/connector/gitlab/gitlab.go
+++ b/connector/gitlab/gitlab.go
@@ -147,10 +147,11 @@ func (c *gitlabConnector) HandleCallback(s connector.Scopes, r *http.Request) (i
 		username = user.Email
 	}
 	identity = connector.Identity{
-		UserID:        strconv.Itoa(user.ID),
-		Username:      username,
-		Email:         user.Email,
-		EmailVerified: true,
+		UserID:            strconv.Itoa(user.ID),
+		Username:          username,
+		PreferredUsername: user.Username,
+		Email:             user.Email,
+		EmailVerified:     true,
 	}
 	if c.useLoginAsID {
 		identity.UserID = user.Username
@@ -197,6 +198,7 @@ func (c *gitlabConnector) Refresh(ctx context.Context, s connector.Scopes, ident
 		username = user.Email
 	}
 	ident.Username = username
+	ident.PreferredUsername = user.Username
 	ident.Email = user.Email
 
 	if c.groupsRequired(s.Groups) {
diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go
index cf8e5006ff28a11bafc58eec0b7ab8e08dbd714d..aed73194a74f753f0631651956080b07ab73eca1 100644
--- a/connector/ldap/ldap.go
+++ b/connector/ldap/ldap.go
@@ -39,6 +39,7 @@ import (
 //         idAttr: uid
 //         emailAttr: mail
 //         nameAttr: name
+//         preferredUsernameAttr: uid
 //       groupSearch:
 //         # Would translate to the query "(&(objectClass=group)(member=<user uid>))"
 //         baseDN: cn=groups,dc=example,dc=com
@@ -103,9 +104,10 @@ type Config struct {
 		Scope string `json:"scope"`
 
 		// A mapping of attributes on the user entry to claims.
-		IDAttr    string `json:"idAttr"`    // Defaults to "uid"
-		EmailAttr string `json:"emailAttr"` // Defaults to "mail"
-		NameAttr  string `json:"nameAttr"`  // No default.
+		IDAttr                    string `json:"idAttr"`                // Defaults to "uid"
+		EmailAttr                 string `json:"emailAttr"`             // Defaults to "mail"
+		NameAttr                  string `json:"nameAttr"`              // No default.
+		PreferredUsernameAttrAttr string `json:"preferredUsernameAttr"` // No default.
 
 		// If this is set, the email claim of the id token will be constructed from the idAttr and
 		// value of emailSuffix. This should not include the @ character.
@@ -341,6 +343,12 @@ func (c *ldapConnector) identityFromEntry(user ldap.Entry) (ident connector.Iden
 		}
 	}
 
+	if c.UserSearch.PreferredUsernameAttrAttr != "" {
+		if ident.PreferredUsername = getAttr(user, c.UserSearch.PreferredUsernameAttrAttr); ident.PreferredUsername == "" {
+			missing = append(missing, c.UserSearch.PreferredUsernameAttrAttr)
+		}
+	}
+
 	if c.UserSearch.EmailSuffix != "" {
 		ident.Email = ident.Username + "@" + c.UserSearch.EmailSuffix
 	} else if ident.Email = getAttr(user, c.UserSearch.EmailAttr); ident.Email == "" {
@@ -381,6 +389,10 @@ func (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.E
 		req.Attributes = append(req.Attributes, c.UserSearch.NameAttr)
 	}
 
+	if c.UserSearch.PreferredUsernameAttrAttr != "" {
+		req.Attributes = append(req.Attributes, c.UserSearch.PreferredUsernameAttrAttr)
+	}
+
 	c.logger.Infof("performing ldap search %s %s %s",
 		req.BaseDN, scopeString(req.Scope), req.Filter)
 	resp, err := conn.Search(req)
diff --git a/server/handlers.go b/server/handlers.go
index 1391e58997d52348b7c174bac467142cdde7cefb..ba34934a2fb1281a286296652e637c95320f95c8 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -479,11 +479,12 @@ func (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request)
 // the approval page's path.
 func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.AuthRequest, conn connector.Connector) (string, error) {
 	claims := storage.Claims{
-		UserID:        identity.UserID,
-		Username:      identity.Username,
-		Email:         identity.Email,
-		EmailVerified: identity.EmailVerified,
-		Groups:        identity.Groups,
+		UserID:            identity.UserID,
+		Username:          identity.Username,
+		PreferredUsername: identity.PreferredUsername,
+		Email:             identity.Email,
+		EmailVerified:     identity.EmailVerified,
+		Groups:            identity.Groups,
 	}
 
 	updater := func(a storage.AuthRequest) (storage.AuthRequest, error) {
@@ -501,8 +502,8 @@ func (s *Server) finalizeLogin(identity connector.Identity, authReq storage.Auth
 		email = email + " (unverified)"
 	}
 
-	s.logger.Infof("login successful: connector %q, username=%q, email=%q, groups=%q",
-		authReq.ConnectorID, claims.Username, email, claims.Groups)
+	s.logger.Infof("login successful: connector %q, username=%q, preferred_username=%q, email=%q, groups=%q",
+		authReq.ConnectorID, claims.Username, claims.PreferredUsername, claims.Email, claims.Groups)
 
 	return path.Join(s.issuerURL.Path, "/approval") + "?req=" + authReq.ID, nil
 }
@@ -992,11 +993,12 @@ func (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, clie
 	}
 
 	claims := storage.Claims{
-		UserID:        ident.UserID,
-		Username:      ident.Username,
-		Email:         ident.Email,
-		EmailVerified: ident.EmailVerified,
-		Groups:        ident.Groups,
+		UserID:            ident.UserID,
+		Username:          ident.Username,
+		PreferredUsername: ident.PreferredUsername,
+		Email:             ident.Email,
+		EmailVerified:     ident.EmailVerified,
+		Groups:            ident.Groups,
 	}
 
 	accessToken, err := s.newAccessToken(client.ID, claims, scopes, refresh.Nonce, refresh.ConnectorID)
diff --git a/server/oauth2.go b/server/oauth2.go
index 6104b549882691ec1d8611d24b9f1a661f93114e..0cd26814ee10cb69e5ef2ba4f17853b6a276932e 100644
--- a/server/oauth2.go
+++ b/server/oauth2.go
@@ -258,7 +258,8 @@ type idTokenClaims struct {
 
 	Groups []string `json:"groups,omitempty"`
 
-	Name string `json:"name,omitempty"`
+	Name              string `json:"name,omitempty"`
+	PreferredUsername string `json:"preferred_username,omitempty"`
 
 	FederatedIDClaims *federatedIDClaims `json:"federated_claims,omitempty"`
 }
@@ -329,6 +330,7 @@ func (s *Server) newIDToken(clientID string, claims storage.Claims, scopes []str
 			tok.Groups = claims.Groups
 		case scope == scopeProfile:
 			tok.Name = claims.Username
+			tok.PreferredUsername = claims.PreferredUsername
 		case scope == scopeFederatedID:
 			tok.FederatedIDClaims = &federatedIDClaims{
 				ConnectorID: connID,
diff --git a/storage/etcd/types.go b/storage/etcd/types.go
index 0d8f521ad4929e4ee8eb03dedf4076ac29181acd..8063c69f593ee61664e29d7ae81f90c5a23fd31b 100644
--- a/storage/etcd/types.go
+++ b/storage/etcd/types.go
@@ -148,30 +148,33 @@ func fromStorageRefreshToken(r storage.RefreshToken) RefreshToken {
 
 // Claims is a mirrored struct from storage with JSON struct tags.
 type Claims struct {
-	UserID        string   `json:"userID"`
-	Username      string   `json:"username"`
-	Email         string   `json:"email"`
-	EmailVerified bool     `json:"emailVerified"`
-	Groups        []string `json:"groups,omitempty"`
+	UserID            string   `json:"userID"`
+	Username          string   `json:"username"`
+	PreferredUsername string   `json:"preferredUsername"`
+	Email             string   `json:"email"`
+	EmailVerified     bool     `json:"emailVerified"`
+	Groups            []string `json:"groups,omitempty"`
 }
 
 func fromStorageClaims(i storage.Claims) Claims {
 	return Claims{
-		UserID:        i.UserID,
-		Username:      i.Username,
-		Email:         i.Email,
-		EmailVerified: i.EmailVerified,
-		Groups:        i.Groups,
+		UserID:            i.UserID,
+		Username:          i.Username,
+		PreferredUsername: i.PreferredUsername,
+		Email:             i.Email,
+		EmailVerified:     i.EmailVerified,
+		Groups:            i.Groups,
 	}
 }
 
 func toStorageClaims(i Claims) storage.Claims {
 	return storage.Claims{
-		UserID:        i.UserID,
-		Username:      i.Username,
-		Email:         i.Email,
-		EmailVerified: i.EmailVerified,
-		Groups:        i.Groups,
+		UserID:            i.UserID,
+		Username:          i.Username,
+		PreferredUsername: i.PreferredUsername,
+		Email:             i.Email,
+		EmailVerified:     i.EmailVerified,
+		Groups:            i.Groups,
 	}
 }
 
diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go
index 1ed405b50a1908f65b9d25cc6a515f490a5e6840..a42238b38c4dffce60d49c1baa2c8c42ebe3e1b4 100644
--- a/storage/kubernetes/types.go
+++ b/storage/kubernetes/types.go
@@ -210,30 +210,33 @@ func toStorageClient(c Client) storage.Client {
 
 // Claims is a mirrored struct from storage with JSON struct tags.
 type Claims struct {
-	UserID        string   `json:"userID"`
-	Username      string   `json:"username"`
-	Email         string   `json:"email"`
-	EmailVerified bool     `json:"emailVerified"`
-	Groups        []string `json:"groups,omitempty"`
+	UserID            string   `json:"userID"`
+	Username          string   `json:"username"`
+	PreferredUsername string   `json:"preferredUsername"`
+	Email             string   `json:"email"`
+	EmailVerified     bool     `json:"emailVerified"`
+	Groups            []string `json:"groups,omitempty"`
 }
 
 func fromStorageClaims(i storage.Claims) Claims {
 	return Claims{
-		UserID:        i.UserID,
-		Username:      i.Username,
-		Email:         i.Email,
-		EmailVerified: i.EmailVerified,
-		Groups:        i.Groups,
+		UserID:            i.UserID,
+		Username:          i.Username,
+		PreferredUsername: i.PreferredUsername,
+		Email:             i.Email,
+		EmailVerified:     i.EmailVerified,
+		Groups:            i.Groups,
 	}
 }
 
 func toStorageClaims(i Claims) storage.Claims {
 	return storage.Claims{
-		UserID:        i.UserID,
-		Username:      i.Username,
-		Email:         i.Email,
-		EmailVerified: i.EmailVerified,
-		Groups:        i.Groups,
+		UserID:            i.UserID,
+		Username:          i.Username,
+		PreferredUsername: i.PreferredUsername,
+		Email:             i.Email,
+		EmailVerified:     i.EmailVerified,
+		Groups:            i.Groups,
 	}
 }
 
diff --git a/storage/sql/crud.go b/storage/sql/crud.go
index d7c055ab186c69f2714c241c738abfca8279ae22..e1982928ec011f9de24c32c615911aa76262d045 100644
--- a/storage/sql/crud.go
+++ b/storage/sql/crud.go
@@ -108,19 +108,19 @@ func (c *conn) CreateAuthRequest(a storage.AuthRequest) error {
 		insert into auth_request (
 			id, client_id, response_types, scopes, redirect_uri, nonce, state,
 			force_approval_prompt, logged_in,
-			claims_user_id, claims_username, claims_email, claims_email_verified,
-			claims_groups,
+			claims_user_id, claims_username, claims_preferred_username, 
+			claims_email, claims_email_verified, claims_groups,
 			connector_id, connector_data,
 			expiry
 		)
 		values (
-			$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17
+			$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18
 		);
 	`,
 		a.ID, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State,
 		a.ForceApprovalPrompt, a.LoggedIn,
-		a.Claims.UserID, a.Claims.Username, a.Claims.Email, a.Claims.EmailVerified,
-		encoder(a.Claims.Groups),
+		a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername,
+		a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups),
 		a.ConnectorID, a.ConnectorData,
 		a.Expiry,
 	)
@@ -149,16 +149,17 @@ func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest)
 			set
 				client_id = $1, response_types = $2, scopes = $3, redirect_uri = $4,
 				nonce = $5, state = $6, force_approval_prompt = $7, logged_in = $8,
-				claims_user_id = $9, claims_username = $10, claims_email = $11,
-				claims_email_verified = $12,
-				claims_groups = $13,
-				connector_id = $14, connector_data = $15,
-				expiry = $16
-			where id = $17;
+				claims_user_id = $9, claims_username = $10, claims_preferred_username = $11,
+				claims_email = $12, claims_email_verified = $13,
+				claims_groups = $14,
+				connector_id = $15, connector_data = $16,
+				expiry = $17
+			where id = $18;
 		`,
 			a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State,
 			a.ForceApprovalPrompt, a.LoggedIn,
-			a.Claims.UserID, a.Claims.Username, a.Claims.Email, a.Claims.EmailVerified,
+			a.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername,
+			a.Claims.Email, a.Claims.EmailVerified,
 			encoder(a.Claims.Groups),
 			a.ConnectorID, a.ConnectorData,
 			a.Expiry, r.ID,
@@ -180,14 +181,15 @@ func getAuthRequest(q querier, id string) (a storage.AuthRequest, err error) {
 		select 
 			id, client_id, response_types, scopes, redirect_uri, nonce, state,
 			force_approval_prompt, logged_in,
-			claims_user_id, claims_username, claims_email, claims_email_verified,
-			claims_groups,
+			claims_user_id, claims_username, claims_preferred_username,
+			claims_email, claims_email_verified, claims_groups,
 			connector_id, connector_data, expiry
 		from auth_request where id = $1;
 	`, id).Scan(
 		&a.ID, &a.ClientID, decoder(&a.ResponseTypes), decoder(&a.Scopes), &a.RedirectURI, &a.Nonce, &a.State,
 		&a.ForceApprovalPrompt, &a.LoggedIn,
-		&a.Claims.UserID, &a.Claims.Username, &a.Claims.Email, &a.Claims.EmailVerified,
+		&a.Claims.UserID, &a.Claims.Username, &a.Claims.PreferredUsername,
+		&a.Claims.Email, &a.Claims.EmailVerified,
 		decoder(&a.Claims.Groups),
 		&a.ConnectorID, &a.ConnectorData, &a.Expiry,
 	)
@@ -204,16 +206,16 @@ func (c *conn) CreateAuthCode(a storage.AuthCode) error {
 	_, err := c.Exec(`
 		insert into auth_code (
 			id, client_id, scopes, nonce, redirect_uri,
-			claims_user_id, claims_username,
+			claims_user_id, claims_username, claims_preferred_username,
 			claims_email, claims_email_verified, claims_groups,
 			connector_id, connector_data,
 			expiry
 		)
-		values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);
+		values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);
 	`,
 		a.ID, a.ClientID, encoder(a.Scopes), a.Nonce, a.RedirectURI, a.Claims.UserID,
-		a.Claims.Username, a.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups),
-		a.ConnectorID, a.ConnectorData, a.Expiry,
+		a.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified,
+		encoder(a.Claims.Groups), a.ConnectorID, a.ConnectorData, a.Expiry,
 	)
 
 	if err != nil {
@@ -229,15 +231,15 @@ func (c *conn) GetAuthCode(id string) (a storage.AuthCode, err error) {
 	err = c.QueryRow(`
 		select
 			id, client_id, scopes, nonce, redirect_uri,
-			claims_user_id, claims_username,
+			claims_user_id, claims_username, claims_preferred_username,
 			claims_email, claims_email_verified, claims_groups,
 			connector_id, connector_data,
 			expiry
 		from auth_code where id = $1;
 	`, id).Scan(
 		&a.ID, &a.ClientID, decoder(&a.Scopes), &a.Nonce, &a.RedirectURI, &a.Claims.UserID,
-		&a.Claims.Username, &a.Claims.Email, &a.Claims.EmailVerified, decoder(&a.Claims.Groups),
-		&a.ConnectorID, &a.ConnectorData, &a.Expiry,
+		&a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified,
+		decoder(&a.Claims.Groups), &a.ConnectorID, &a.ConnectorData, &a.Expiry,
 	)
 	if err != nil {
 		if err == sql.ErrNoRows {
@@ -252,15 +254,16 @@ func (c *conn) CreateRefresh(r storage.RefreshToken) error {
 	_, err := c.Exec(`
 		insert into refresh_token (
 			id, client_id, scopes, nonce,
-			claims_user_id, claims_username, claims_email, claims_email_verified,
-			claims_groups,
+			claims_user_id, claims_username, claims_preferred_username,
+			claims_email, claims_email_verified, claims_groups,
 			connector_id, connector_data,
 			token, created_at, last_used
 		)
-		values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);
+		values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15);
 	`,
 		r.ID, r.ClientID, encoder(r.Scopes), r.Nonce,
-		r.Claims.UserID, r.Claims.Username, r.Claims.Email, r.Claims.EmailVerified,
+		r.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername,
+		r.Claims.Email, r.Claims.EmailVerified,
 		encoder(r.Claims.Groups),
 		r.ConnectorID, r.ConnectorData,
 		r.Token, r.CreatedAt, r.LastUsed,
@@ -291,19 +294,21 @@ func (c *conn) UpdateRefreshToken(id string, updater func(old storage.RefreshTok
 				nonce = $3,
 				claims_user_id = $4,
 				claims_username = $5,
-				claims_email = $6,
-				claims_email_verified = $7,
-				claims_groups = $8,
-				connector_id = $9,
-				connector_data = $10,
-				token = $11,
-				created_at = $12,
-				last_used = $13
+				claims_preferred_username = $6,
+				claims_email = $7,
+				claims_email_verified = $8,
+				claims_groups = $9,
+				connector_id = $10,
+				connector_data = $11,
+				token = $12,
+				created_at = $13,
+				last_used = $14
 			where
-				id = $14
+				id = $15
 		`,
 			r.ClientID, encoder(r.Scopes), r.Nonce,
-			r.Claims.UserID, r.Claims.Username, r.Claims.Email, r.Claims.EmailVerified,
+			r.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername,
+			r.Claims.Email, r.Claims.EmailVerified,
 			encoder(r.Claims.Groups),
 			r.ConnectorID, r.ConnectorData,
 			r.Token, r.CreatedAt, r.LastUsed, id,
@@ -323,7 +328,8 @@ func getRefresh(q querier, id string) (storage.RefreshToken, error) {
 	return scanRefresh(q.QueryRow(`
 		select
 			id, client_id, scopes, nonce,
-			claims_user_id, claims_username, claims_email, claims_email_verified,
+			claims_user_id, claims_username, claims_preferred_username,
+			claims_email, claims_email_verified,
 			claims_groups,
 			connector_id, connector_data,
 			token, created_at, last_used
@@ -335,8 +341,8 @@ func (c *conn) ListRefreshTokens() ([]storage.RefreshToken, error) {
 	rows, err := c.Query(`
 		select
 			id, client_id, scopes, nonce,
-			claims_user_id, claims_username, claims_email, claims_email_verified,
-			claims_groups,
+			claims_user_id, claims_username, claims_preferred_username,
+			claims_email, claims_email_verified, claims_groups,
 			connector_id, connector_data,
 			token, created_at, last_used
 		from refresh_token;
@@ -361,7 +367,8 @@ func (c *conn) ListRefreshTokens() ([]storage.RefreshToken, error) {
 func scanRefresh(s scanner) (r storage.RefreshToken, err error) {
 	err = s.Scan(
 		&r.ID, &r.ClientID, decoder(&r.Scopes), &r.Nonce,
-		&r.Claims.UserID, &r.Claims.Username, &r.Claims.Email, &r.Claims.EmailVerified,
+		&r.Claims.UserID, &r.Claims.Username, &r.Claims.PreferredUsername,
+		&r.Claims.Email, &r.Claims.EmailVerified,
 		decoder(&r.Claims.Groups),
 		&r.ConnectorID, &r.ConnectorData,
 		&r.Token, &r.CreatedAt, &r.LastUsed,
diff --git a/storage/sql/migrate.go b/storage/sql/migrate.go
index e30629e742ab150f743b7db94a756d18a6c0753f..0ef62609a658ef14f8fe0e240f4bb231681a54f8 100644
--- a/storage/sql/migrate.go
+++ b/storage/sql/migrate.go
@@ -190,4 +190,16 @@ var migrations = []migration{
 			);`,
 		},
 	},
+	{
+		stmts: []string{`
+			alter table auth_code
+				add column claims_preferred_username text not null default '';`,
+			`
+			alter table auth_request
+				add column claims_preferred_username text not null default '';`,
+			`
+			alter table refresh_token
+				add column claims_preferred_username text not null default '';`,
+		},
+	},
 }
diff --git a/storage/storage.go b/storage/storage.go
index 893fb1003578f2271d259b9835552d369568f697..235f74e07a0b94ea8156d65d31b53ddce5f5bde6 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -137,10 +137,11 @@ type Client struct {
 
 // Claims represents the ID Token claims supported by the server.
 type Claims struct {
-	UserID        string
-	Username      string
-	Email         string
-	EmailVerified bool
+	UserID            string
+	Username          string
+	PreferredUsername string
+	Email             string
+	EmailVerified     bool
 
 	Groups []string
 }