diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index bda0668c45f5..53a5c28b4a59 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -51,14 +51,6 @@ func (app *OAuth2Application) TableName() string {
 	return "oauth2_application"
 }
 
-// PrimaryRedirectURI returns the first redirect uri or an empty string if empty
-func (app *OAuth2Application) PrimaryRedirectURI() string {
-	if len(app.RedirectURIs) == 0 {
-		return ""
-	}
-	return app.RedirectURIs[0]
-}
-
 // ContainsRedirectURI checks if redirectURI is allowed for app
 func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
 	if !app.ConfidentialClient {
diff --git a/modules/util/truncate.go b/modules/util/truncate.go
index f41d27d8b743..77b116eeff27 100644
--- a/modules/util/truncate.go
+++ b/modules/util/truncate.go
@@ -3,7 +3,10 @@
 
 package util
 
-import "unicode/utf8"
+import (
+	"strings"
+	"unicode/utf8"
+)
 
 // in UTF8 "…" is 3 bytes so doesn't really gain us anything...
 const (
@@ -35,3 +38,17 @@ func SplitStringAtByteN(input string, n int) (left, right string) {
 
 	return input[:end] + utf8Ellipsis, utf8Ellipsis + input[end:]
 }
+
+// SplitTrimSpace splits the string at given separator and trims leading and trailing space
+func SplitTrimSpace(input, sep string) []string {
+	// replace CRLF with LF
+	input = strings.ReplaceAll(input, "\r\n", "\n")
+
+	var stringList []string
+	for _, s := range strings.Split(input, sep) {
+		// trim leading and trailing space
+		stringList = append(stringList, strings.TrimSpace(s))
+	}
+
+	return stringList
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 0abf1b3e0582..2245d9bae0e9 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -826,7 +826,7 @@ create_oauth2_application_success = You've successfully created a new OAuth2 app
 update_oauth2_application_success = You've successfully updated the OAuth2 application.
 oauth2_application_name = Application Name
 oauth2_confidential_client = Confidential Client. Select for apps that keep the secret confidential, such as web apps. Do not select for native apps including desktop and mobile apps.
-oauth2_redirect_uri = Redirect URI
+oauth2_redirect_uris = Redirect URIs. Please use a new line for every URI.
 save_application = Save
 oauth2_client_id = Client ID
 oauth2_client_secret = Client Secret
diff --git a/routers/web/user/setting/oauth2_common.go b/routers/web/user/setting/oauth2_common.go
index 5489b6026088..5de0f0e22f42 100644
--- a/routers/web/user/setting/oauth2_common.go
+++ b/routers/web/user/setting/oauth2_common.go
@@ -10,6 +10,7 @@ import (
 	"code.gitea.io/gitea/models/auth"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/context"
+	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/services/forms"
 )
@@ -40,7 +41,7 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) {
 	// TODO validate redirect URI
 	app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{
 		Name:               form.Name,
-		RedirectURIs:       []string{form.RedirectURI},
+		RedirectURIs:       util.SplitTrimSpace(form.RedirectURIs, "\n"),
 		UserID:             oa.OwnerID,
 		ConfidentialClient: form.ConfidentialClient,
 	})
@@ -93,7 +94,7 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) {
 	if ctx.Data["App"], err = auth.UpdateOAuth2Application(auth.UpdateOAuth2ApplicationOptions{
 		ID:                 ctx.ParamsInt64("id"),
 		Name:               form.Name,
-		RedirectURIs:       []string{form.RedirectURI},
+		RedirectURIs:       util.SplitTrimSpace(form.RedirectURIs, "\n"),
 		UserID:             oa.OwnerID,
 		ConfidentialClient: form.ConfidentialClient,
 	}); err != nil {
diff --git a/services/forms/user_form.go b/services/forms/user_form.go
index 1e04f85319ca..1315fb237b3b 100644
--- a/services/forms/user_form.go
+++ b/services/forms/user_form.go
@@ -398,7 +398,7 @@ func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) {
 // EditOAuth2ApplicationForm form for editing oauth2 applications
 type EditOAuth2ApplicationForm struct {
 	Name               string `binding:"Required;MaxSize(255)" form:"application_name"`
-	RedirectURI        string `binding:"Required" form:"redirect_uri"`
+	RedirectURIs       string `binding:"Required" form:"redirect_uris"`
 	ConfidentialClient bool   `form:"confidential_client"`
 }
 
diff --git a/templates/user/settings/applications_oauth2_edit_form.tmpl b/templates/user/settings/applications_oauth2_edit_form.tmpl
index 45f12f22d05d..e19efe3147bc 100644
--- a/templates/user/settings/applications_oauth2_edit_form.tmpl
+++ b/templates/user/settings/applications_oauth2_edit_form.tmpl
@@ -39,8 +39,8 @@
 				<input id="application-name" value="{{.App.Name}}" name="application_name" required maxlength="255">
 			</div>
 			<div class="field {{if .Err_RedirectURI}}error{{end}}">
-				<label for="redirect-uri">{{.locale.Tr "settings.oauth2_redirect_uri"}}</label>
-				<input type="url" name="redirect_uri" value="{{.App.PrimaryRedirectURI}}" id="redirect-uri" required>
+				<label for="redirect-uris">{{.locale.Tr "settings.oauth2_redirect_uris"}}</label>
+				<textarea name="redirect_uris" id="redirect-uris" required>{{StringUtils.Join .App.RedirectURIs "\n"}}</textarea>
 			</div>
 			<div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}">
 				<label>{{.locale.Tr "settings.oauth2_confidential_client"}}</label>
diff --git a/templates/user/settings/applications_oauth2_list.tmpl b/templates/user/settings/applications_oauth2_list.tmpl
index 2e42ed275877..e18c7f56b833 100644
--- a/templates/user/settings/applications_oauth2_list.tmpl
+++ b/templates/user/settings/applications_oauth2_list.tmpl
@@ -34,8 +34,8 @@
 			<input id="application-name" name="application_name" value="{{.application_name}}" required maxlength="255">
 		</div>
 		<div class="field {{if .Err_RedirectURI}}error{{end}}">
-			<label for="redirect-uri">{{.locale.Tr "settings.oauth2_redirect_uri"}}</label>
-			<input type="url" name="redirect_uri" id="redirect-uri">
+			<label for="redirect-uris">{{.locale.Tr "settings.oauth2_redirect_uris"}}</label>
+			<textarea name="redirect_uris" id="redirect-uris"></textarea>
 		</div>
 		<div class="field ui checkbox {{if .Err_ConfidentialClient}}error{{end}}">
 			<label>{{.locale.Tr "settings.oauth2_confidential_client"}}</label>