diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go
index 696d3b9a53..b3fead7da9 100644
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -56,8 +56,10 @@ func (f *CreateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bin
 type MigrateRepoForm struct {
 	// required: true
 	CloneAddr    string `json:"clone_addr" binding:"Required"`
+	Service      int    `json:"service"`
 	AuthUsername string `json:"auth_username"`
 	AuthPassword string `json:"auth_password"`
+	AuthToken    string `json:"auth_token"`
 	// required: true
 	UID int64 `json:"uid" binding:"Required"`
 	// required: true
diff --git a/modules/migrations/base/downloader.go b/modules/migrations/base/downloader.go
index c31f3df1d1..b692969ba5 100644
--- a/modules/migrations/base/downloader.go
+++ b/modules/migrations/base/downloader.go
@@ -7,13 +7,20 @@ package base
 
 import (
 	"context"
+	"io"
 	"time"
 
 	"code.gitea.io/gitea/modules/structs"
 )
 
+// AssetDownloader downloads an asset (attachment) for a release
+type AssetDownloader interface {
+	GetAsset(tag string, id int64) (io.ReadCloser, error)
+}
+
 // Downloader downloads the site repo informations
 type Downloader interface {
+	AssetDownloader
 	SetContext(context.Context)
 	GetRepoInfo() (*Repository, error)
 	GetTopics() ([]string, error)
@@ -28,7 +35,6 @@ type Downloader interface {
 
 // DownloaderFactory defines an interface to match a downloader implementation and create a downloader
 type DownloaderFactory interface {
-	Match(opts MigrateOptions) (bool, error)
 	New(opts MigrateOptions) (Downloader, error)
 	GitServiceType() structs.GitServiceType
 }
diff --git a/modules/migrations/base/release.go b/modules/migrations/base/release.go
index b2541f1bf5..2a223920c7 100644
--- a/modules/migrations/base/release.go
+++ b/modules/migrations/base/release.go
@@ -8,7 +8,7 @@ import "time"
 
 // ReleaseAsset represents a release asset
 type ReleaseAsset struct {
-	URL           string
+	ID            int64
 	Name          string
 	ContentType   *string
 	Size          *int
diff --git a/modules/migrations/base/uploader.go b/modules/migrations/base/uploader.go
index 85ad60fe0e..07c2bb0d42 100644
--- a/modules/migrations/base/uploader.go
+++ b/modules/migrations/base/uploader.go
@@ -11,7 +11,7 @@ type Uploader interface {
 	CreateRepo(repo *Repository, opts MigrateOptions) error
 	CreateTopics(topic ...string) error
 	CreateMilestones(milestones ...*Milestone) error
-	CreateReleases(releases ...*Release) error
+	CreateReleases(downloader Downloader, releases ...*Release) error
 	SyncTags() error
 	CreateLabels(labels ...*Label) error
 	CreateIssues(issues ...*Issue) error
diff --git a/modules/migrations/git.go b/modules/migrations/git.go
index af345808b5..5c9acb2533 100644
--- a/modules/migrations/git.go
+++ b/modules/migrations/git.go
@@ -6,6 +6,7 @@ package migrations
 
 import (
 	"context"
+	"io"
 
 	"code.gitea.io/gitea/modules/migrations/base"
 )
@@ -64,6 +65,11 @@ func (g *PlainGitDownloader) GetReleases() ([]*base.Release, error) {
 	return nil, ErrNotSupported
 }
 
+// GetAsset returns an asset
+func (g *PlainGitDownloader) GetAsset(_ string, _ int64) (io.ReadCloser, error) {
+	return nil, ErrNotSupported
+}
+
 // GetIssues returns issues according page and perPage
 func (g *PlainGitDownloader) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
 	return nil, false, ErrNotSupported
diff --git a/modules/migrations/gitea.go b/modules/migrations/gitea.go
index 8c097e143c..082ddcd5fb 100644
--- a/modules/migrations/gitea.go
+++ b/modules/migrations/gitea.go
@@ -93,12 +93,15 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
 	}
 
 	var remoteAddr = repo.CloneURL
-	if len(opts.AuthUsername) > 0 {
+	if len(opts.AuthToken) > 0 || len(opts.AuthUsername) > 0 {
 		u, err := url.Parse(repo.CloneURL)
 		if err != nil {
 			return err
 		}
 		u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
+		if len(opts.AuthToken) > 0 {
+			u.User = url.UserPassword("oauth2", opts.AuthToken)
+		}
 		remoteAddr = u.String()
 	}
 
@@ -210,7 +213,7 @@ func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error {
 }
 
 // CreateReleases creates releases
-func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
+func (g *GiteaLocalUploader) CreateReleases(downloader base.Downloader, releases ...*base.Release) error {
 	var rels = make([]*models.Release, 0, len(releases))
 	for _, release := range releases {
 		var rel = models.Release{
@@ -269,13 +272,11 @@ func (g *GiteaLocalUploader) CreateReleases(releases ...*base.Release) error {
 
 			// download attachment
 			err = func() error {
-				resp, err := http.Get(asset.URL)
+				rc, err := downloader.GetAsset(rel.TagName, asset.ID)
 				if err != nil {
 					return err
 				}
-				defer resp.Body.Close()
-
-				_, err = storage.Attachments.Save(attach.RelativePath(), resp.Body)
+				_, err = storage.Attachments.Save(attach.RelativePath(), rc)
 				return err
 			}()
 			if err != nil {
diff --git a/modules/migrations/gitea_test.go b/modules/migrations/gitea_test.go
index c0d2dcd180..02b2f0a5c9 100644
--- a/modules/migrations/gitea_test.go
+++ b/modules/migrations/gitea_test.go
@@ -26,7 +26,7 @@ func TestGiteaUploadRepo(t *testing.T) {
 	user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User)
 
 	var (
-		downloader = NewGithubDownloaderV3("", "", "go-xorm", "builder")
+		downloader = NewGithubDownloaderV3("", "", "", "go-xorm", "builder")
 		repoName   = "builder-" + time.Now().Format("2006-01-02-15-04-05")
 		uploader   = NewGiteaLocalUploader(graceful.GetManager().HammerContext(), user, user.Name, repoName)
 	)
diff --git a/modules/migrations/github.go b/modules/migrations/github.go
index 97d62b994f..eb73a7e0d4 100644
--- a/modules/migrations/github.go
+++ b/modules/migrations/github.go
@@ -6,8 +6,11 @@
 package migrations
 
 import (
+	"bytes"
 	"context"
 	"fmt"
+	"io"
+	"io/ioutil"
 	"net/http"
 	"net/url"
 	"strings"
@@ -37,16 +40,6 @@ func init() {
 type GithubDownloaderV3Factory struct {
 }
 
-// Match returns ture if the migration remote URL matched this downloader factory
-func (f *GithubDownloaderV3Factory) Match(opts base.MigrateOptions) (bool, error) {
-	u, err := url.Parse(opts.CloneAddr)
-	if err != nil {
-		return false, err
-	}
-
-	return strings.EqualFold(u.Host, "github.com") && opts.AuthUsername != "", nil
-}
-
 // New returns a Downloader related to this factory according MigrateOptions
 func (f *GithubDownloaderV3Factory) New(opts base.MigrateOptions) (base.Downloader, error) {
 	u, err := url.Parse(opts.CloneAddr)
@@ -60,7 +53,7 @@ func (f *GithubDownloaderV3Factory) New(opts base.MigrateOptions) (base.Download
 
 	log.Trace("Create github downloader: %s/%s", oldOwner, oldName)
 
-	return NewGithubDownloaderV3(opts.AuthUsername, opts.AuthPassword, oldOwner, oldName), nil
+	return NewGithubDownloaderV3(opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
 }
 
 // GitServiceType returns the type of git service
@@ -81,7 +74,7 @@ type GithubDownloaderV3 struct {
 }
 
 // NewGithubDownloaderV3 creates a github Downloader via github v3 API
-func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *GithubDownloaderV3 {
+func NewGithubDownloaderV3(userName, password, token, repoOwner, repoName string) *GithubDownloaderV3 {
 	var downloader = GithubDownloaderV3{
 		userName:  userName,
 		password:  password,
@@ -90,23 +83,19 @@ func NewGithubDownloaderV3(userName, password, repoOwner, repoName string) *Gith
 		repoName:  repoName,
 	}
 
-	var client *http.Client
-	if userName != "" {
-		if password == "" {
-			ts := oauth2.StaticTokenSource(
-				&oauth2.Token{AccessToken: userName},
-			)
-			client = oauth2.NewClient(downloader.ctx, ts)
-		} else {
-			client = &http.Client{
-				Transport: &http.Transport{
-					Proxy: func(req *http.Request) (*url.URL, error) {
-						req.SetBasicAuth(userName, password)
-						return nil, nil
-					},
-				},
-			}
-		}
+	client := &http.Client{
+		Transport: &http.Transport{
+			Proxy: func(req *http.Request) (*url.URL, error) {
+				req.SetBasicAuth(userName, password)
+				return nil, nil
+			},
+		},
+	}
+	if token != "" {
+		ts := oauth2.StaticTokenSource(
+			&oauth2.Token{AccessToken: token},
+		)
+		client = oauth2.NewClient(downloader.ctx, ts)
 	}
 	downloader.client = github.NewClient(client)
 	return &downloader
@@ -290,10 +279,8 @@ func (g *GithubDownloaderV3) convertGithubRelease(rel *github.RepositoryRelease)
 	}
 
 	for _, asset := range rel.Assets {
-		u, _ := url.Parse(*asset.BrowserDownloadURL)
-		u.User = url.UserPassword(g.userName, g.password)
 		r.Assets = append(r.Assets, base.ReleaseAsset{
-			URL:           u.String(),
+			ID:            *asset.ID,
 			Name:          *asset.Name,
 			ContentType:   asset.ContentType,
 			Size:          asset.Size,
@@ -331,6 +318,18 @@ func (g *GithubDownloaderV3) GetReleases() ([]*base.Release, error) {
 	return releases, nil
 }
 
+// GetAsset returns an asset
+func (g *GithubDownloaderV3) GetAsset(_ string, id int64) (io.ReadCloser, error) {
+	asset, redir, err := g.client.Repositories.DownloadReleaseAsset(g.ctx, g.repoOwner, g.repoName, id, http.DefaultClient)
+	if err != nil {
+		return nil, err
+	}
+	if asset == nil {
+		return ioutil.NopCloser(bytes.NewBufferString(redir)), nil
+	}
+	return asset, nil
+}
+
 // GetIssues returns issues according start and limit
 func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool, error) {
 	opt := &github.IssueListByRepoOptions{
diff --git a/modules/migrations/github_test.go b/modules/migrations/github_test.go
index 814c771e8c..0b8c559d30 100644
--- a/modules/migrations/github_test.go
+++ b/modules/migrations/github_test.go
@@ -64,7 +64,7 @@ func assertLabelEqual(t *testing.T, name, color, description string, label *base
 
 func TestGitHubDownloadRepo(t *testing.T) {
 	GithubLimitRateRemaining = 3 //Wait at 3 remaining since we could have 3 CI in //
-	downloader := NewGithubDownloaderV3(os.Getenv("GITHUB_READ_TOKEN"), "", "go-gitea", "test_repo")
+	downloader := NewGithubDownloaderV3("", "", os.Getenv("GITHUB_READ_TOKEN"), "go-gitea", "test_repo")
 	err := downloader.RefreshRate()
 	assert.NoError(t, err)
 
diff --git a/modules/migrations/gitlab.go b/modules/migrations/gitlab.go
index 4f218c95f1..eec16d2433 100644
--- a/modules/migrations/gitlab.go
+++ b/modules/migrations/gitlab.go
@@ -8,6 +8,8 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"io"
+	"net/http"
 	"net/url"
 	"strings"
 	"time"
@@ -32,21 +34,6 @@ func init() {
 type GitlabDownloaderFactory struct {
 }
 
-// Match returns true if the migration remote URL matched this downloader factory
-func (f *GitlabDownloaderFactory) Match(opts base.MigrateOptions) (bool, error) {
-	var matched bool
-
-	u, err := url.Parse(opts.CloneAddr)
-	if err != nil {
-		return false, err
-	}
-	if strings.EqualFold(u.Host, "gitlab.com") && opts.AuthUsername != "" {
-		matched = true
-	}
-
-	return matched, nil
-}
-
 // New returns a Downloader related to this factory according MigrateOptions
 func (f *GitlabDownloaderFactory) New(opts base.MigrateOptions) (base.Downloader, error) {
 	u, err := url.Parse(opts.CloneAddr)
@@ -56,10 +43,11 @@ func (f *GitlabDownloaderFactory) New(opts base.MigrateOptions) (base.Downloader
 
 	baseURL := u.Scheme + "://" + u.Host
 	repoNameSpace := strings.TrimPrefix(u.Path, "/")
+	repoNameSpace = strings.TrimSuffix(repoNameSpace, ".git")
 
 	log.Trace("Create gitlab downloader. BaseURL: %s RepoName: %s", baseURL, repoNameSpace)
 
-	return NewGitlabDownloader(baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword), nil
+	return NewGitlabDownloader(baseURL, repoNameSpace, opts.AuthUsername, opts.AuthPassword, opts.AuthToken), nil
 }
 
 // GitServiceType returns the type of git service
@@ -85,15 +73,13 @@ type GitlabDownloader struct {
 // NewGitlabDownloader creates a gitlab Downloader via gitlab API
 //   Use either a username/password, personal token entered into the username field, or anonymous/public access
 //   Note: Public access only allows very basic access
-func NewGitlabDownloader(baseURL, repoPath, username, password string) *GitlabDownloader {
+func NewGitlabDownloader(baseURL, repoPath, username, password, token string) *GitlabDownloader {
 	var gitlabClient *gitlab.Client
 	var err error
-	if username != "" {
-		if password == "" {
-			gitlabClient, err = gitlab.NewClient(username, gitlab.WithBaseURL(baseURL))
-		} else {
-			gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL))
-		}
+	if token != "" {
+		gitlabClient, err = gitlab.NewClient(token, gitlab.WithBaseURL(baseURL))
+	} else {
+		gitlabClient, err = gitlab.NewBasicAuthClient(username, password, gitlab.WithBaseURL(baseURL))
 	}
 
 	if err != nil {
@@ -271,7 +257,7 @@ func (g *GitlabDownloader) GetLabels() ([]*base.Label, error) {
 }
 
 func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Release {
-
+	var zero int
 	r := &base.Release{
 		TagName:         rel.TagName,
 		TargetCommitish: rel.Commit.ID,
@@ -284,9 +270,11 @@ func (g *GitlabDownloader) convertGitlabRelease(rel *gitlab.Release) *base.Relea
 
 	for k, asset := range rel.Assets.Links {
 		r.Assets = append(r.Assets, base.ReleaseAsset{
-			URL:         asset.URL,
-			Name:        asset.Name,
-			ContentType: &rel.Assets.Sources[k].Format,
+			ID:            int64(asset.ID),
+			Name:          asset.Name,
+			ContentType:   &rel.Assets.Sources[k].Format,
+			Size:          &zero,
+			DownloadCount: &zero,
 		})
 	}
 	return r
@@ -315,6 +303,21 @@ func (g *GitlabDownloader) GetReleases() ([]*base.Release, error) {
 	return releases, nil
 }
 
+// GetAsset returns an asset
+func (g *GitlabDownloader) GetAsset(tag string, id int64) (io.ReadCloser, error) {
+	link, _, err := g.client.ReleaseLinks.GetReleaseLink(g.repoID, tag, int(id))
+	if err != nil {
+		return nil, err
+	}
+	resp, err := http.Get(link.URL)
+	if err != nil {
+		return nil, err
+	}
+
+	// resp.Body is closed by the uploader
+	return resp.Body, nil
+}
+
 // GetIssues returns issues according start and limit
 //   Note: issue label description and colors are not supported by the go-gitlab library at this time
 //   TODO: figure out how to transfer issue reactions
diff --git a/modules/migrations/gitlab_test.go b/modules/migrations/gitlab_test.go
index 003da5bbdf..daf05f8e3a 100644
--- a/modules/migrations/gitlab_test.go
+++ b/modules/migrations/gitlab_test.go
@@ -27,7 +27,7 @@ func TestGitlabDownloadRepo(t *testing.T) {
 		t.Skipf("Can't access test repo, skipping %s", t.Name())
 	}
 
-	downloader := NewGitlabDownloader("https://gitlab.com", "gitea/test_repo", gitlabPersonalAccessToken, "")
+	downloader := NewGitlabDownloader("https://gitlab.com", "gitea/test_repo", "", "", gitlabPersonalAccessToken)
 	if downloader == nil {
 		t.Fatal("NewGitlabDownloader is nil")
 	}
diff --git a/modules/migrations/migrate.go b/modules/migrations/migrate.go
index c970ba6920..7858dfc685 100644
--- a/modules/migrations/migrate.go
+++ b/modules/migrations/migrate.go
@@ -13,7 +13,6 @@ import (
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/migrations/base"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/structs"
 )
 
 // MigrateOptions is equal to base.MigrateOptions
@@ -33,18 +32,15 @@ func MigrateRepository(ctx context.Context, doer *models.User, ownerName string,
 	var (
 		downloader base.Downloader
 		uploader   = NewGiteaLocalUploader(ctx, doer, ownerName, opts.RepoName)
-		theFactory base.DownloaderFactory
+		err        error
 	)
 
 	for _, factory := range factories {
-		if match, err := factory.Match(opts); err != nil {
-			return nil, err
-		} else if match {
+		if factory.GitServiceType() == opts.GitServiceType {
 			downloader, err = factory.New(opts)
 			if err != nil {
 				return nil, err
 			}
-			theFactory = factory
 			break
 		}
 	}
@@ -57,11 +53,8 @@ func MigrateRepository(ctx context.Context, doer *models.User, ownerName string,
 		opts.Comments = false
 		opts.Issues = false
 		opts.PullRequests = false
-		opts.GitServiceType = structs.PlainGitService
 		downloader = NewPlainGitDownloader(ownerName, opts.RepoName, opts.CloneAddr)
 		log.Trace("Will migrate from git: %s", opts.OriginalURL)
-	} else if opts.GitServiceType == structs.NotMigrated {
-		opts.GitServiceType = theFactory.GitServiceType()
 	}
 
 	uploader.gitServiceType = opts.GitServiceType
@@ -169,7 +162,7 @@ func migrateRepository(downloader base.Downloader, uploader base.Uploader, opts
 				relBatchSize = len(releases)
 			}
 
-			if err := uploader.CreateReleases(releases[:relBatchSize]...); err != nil {
+			if err := uploader.CreateReleases(downloader, releases[:relBatchSize]...); err != nil {
 				return err
 			}
 			releases = releases[relBatchSize:]
diff --git a/modules/structs/repo.go b/modules/structs/repo.go
index 217a6f74ad..808d2ffbc8 100644
--- a/modules/structs/repo.go
+++ b/modules/structs/repo.go
@@ -218,6 +218,32 @@ func (gt GitServiceType) Name() string {
 	return ""
 }
 
+// Title represents the service type's proper title
+func (gt GitServiceType) Title() string {
+	switch gt {
+	case GithubService:
+		return "GitHub"
+	case GiteaService:
+		return "Gitea"
+	case GitlabService:
+		return "GitLab"
+	case GogsService:
+		return "Gogs"
+	case PlainGitService:
+		return "Git"
+	}
+	return ""
+}
+
+// TokenAuth represents whether a service type supports token-based auth
+func (gt GitServiceType) TokenAuth() bool {
+	switch gt {
+	case GithubService, GiteaService, GitlabService:
+		return true
+	}
+	return false
+}
+
 var (
 	// SupportedFullGitService represents all git services supported to migrate issues/labels/prs and etc.
 	// TODO: add to this list after new git service added
@@ -233,6 +259,7 @@ type MigrateRepoOption struct {
 	CloneAddr    string `json:"clone_addr" binding:"Required"`
 	AuthUsername string `json:"auth_username"`
 	AuthPassword string `json:"auth_password"`
+	AuthToken    string `json:"auth_token"`
 	// required: true
 	UID int `json:"uid" binding:"Required"`
 	// required: true
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 388348e95c..5aeffc7580 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -26,6 +26,7 @@ return_to_gitea = Return to Gitea
 username = Username
 email = Email Address
 password = Password
+access_token = Access Token
 re_type = Re-Type Password
 captcha = CAPTCHA
 twofa = Two-Factor Authentication
@@ -707,9 +708,10 @@ form.name_reserved = The repository name '%s' is reserved.
 form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name.
 
 need_auth = Clone Authorization
-migrate_type = Migration Type
-migrate_type_helper = This repository will be a <span class="text blue">mirror</span>
-migrate_type_helper_disabled = Your site administrator has disabled new mirrors.
+migrate_options = Migration Options
+migrate_service = Migration Service
+migrate_options_mirror_helper = This repository will be a <span class="text blue">mirror</span>
+migrate_options_mirror_disabled = Your site administrator has disabled new mirrors.
 migrate_items = Migration Items
 migrate_items_wiki = Wiki
 migrate_items_milestones = Milestones
@@ -725,7 +727,7 @@ migrate.permission_denied = You are not allowed to import local repositories.
 migrate.invalid_local_path = "The local path is invalid. It does not exist or is not a directory."
 migrate.failed = Migration failed: %v
 migrate.lfs_mirror_unsupported = Mirroring LFS objects is not supported - use 'git lfs fetch --all' and 'git lfs push --all' instead.
-migrate.migrate_items_options = When migrating from github, input a username and migration options will be displayed.
+migrate.migrate_items_options = Authentication is needed to migrate items from a service that supports them.
 migrated_from = Migrated from <a href="%[1]s">%[2]s</a>
 migrated_from_fake = Migrated From %[1]s
 migrate.migrating = Migrating from <b>%s</b> ...
diff --git a/routers/repo/repo.go b/routers/repo/repo.go
index 27c8ff1e03..71df2d0cb7 100644
--- a/routers/repo/repo.go
+++ b/routers/repo/repo.go
@@ -7,7 +7,6 @@ package repo
 
 import (
 	"fmt"
-	"net/url"
 	"os"
 	"path"
 	"strings"
@@ -269,6 +268,9 @@ func Migrate(ctx *context.Context) {
 	ctx.Data["pull_requests"] = ctx.Query("pull_requests") == "1"
 	ctx.Data["releases"] = ctx.Query("releases") == "1"
 	ctx.Data["LFSActive"] = setting.LFS.StartServer
+	// Plain git should be first
+	ctx.Data["service"] = structs.PlainGitService
+	ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)
 
 	ctxUser := checkContextUser(ctx, ctx.QueryInt64("org"))
 	if ctx.Written() {
@@ -316,6 +318,9 @@ func handleMigrateError(ctx *context.Context, owner *models.User, err error, nam
 // MigratePost response for migrating from external git repository
 func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
 	ctx.Data["Title"] = ctx.Tr("new_migrate")
+	// Plain git should be first
+	ctx.Data["service"] = structs.PlainGitService
+	ctx.Data["Services"] = append([]structs.GitServiceType{structs.PlainGitService}, structs.SupportedFullGitService...)
 
 	ctxUser := checkContextUser(ctx, form.UID)
 	if ctx.Written() {
@@ -349,15 +354,9 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
 		return
 	}
 
-	var gitServiceType = structs.PlainGitService
-	u, err := url.Parse(form.CloneAddr)
-	if err == nil && strings.EqualFold(u.Host, "github.com") {
-		gitServiceType = structs.GithubService
-	}
-
 	var opts = migrations.MigrateOptions{
 		OriginalURL:    form.CloneAddr,
-		GitServiceType: gitServiceType,
+		GitServiceType: structs.GitServiceType(form.Service),
 		CloneAddr:      remoteAddr,
 		RepoName:       form.RepoName,
 		Description:    form.Description,
@@ -365,6 +364,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) {
 		Mirror:         form.Mirror && !setting.Repository.DisableMirrors,
 		AuthUsername:   form.AuthUsername,
 		AuthPassword:   form.AuthPassword,
+		AuthToken:      form.AuthToken,
 		Wiki:           form.Wiki,
 		Issues:         form.Issues,
 		Milestones:     form.Milestones,
diff --git a/templates/repo/migrate.tmpl b/templates/repo/migrate.tmpl
index 60b432beaa..d5a31a6800 100644
--- a/templates/repo/migrate.tmpl
+++ b/templates/repo/migrate.tmpl
@@ -14,24 +14,83 @@
 						<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
 						<span class="help">
 						{{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}}
-						<br/>{{.i18n.Tr "repo.migrate.migrate_items_options"}}
 						{{if .LFSActive}}<br/>{{.i18n.Tr "repo.migrate.lfs_mirror_unsupported"}}{{end}}
 						</span>
 					</div>
-					<div class="ui accordion optional field">
-						<div class="title {{if .Err_Auth}}text red active{{end}}">
-							<i class="icon dropdown"></i>
-							{{.i18n.Tr "repo.need_auth"}}
-						</div>
-						<div class="content {{if .Err_Auth}}active{{end}}">
-							<div class="inline field {{if .Err_Auth}}error{{end}}">
-								<label for="auth_username">{{.i18n.Tr "username"}}</label>
-								<input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}>
+
+					<div class="inline field">
+						<label>{{.i18n.Tr "repo.migrate_service"}}</label>
+						<div class="ui selection dropdown">
+							<input id="service_type" type="hidden" name="service" value="{{.service}}">
+							<div class="default text"></div>
+							<i class="dropdown icon"></i>
+							<div class="menu">
+								{{range .Services}}
+									<div id="service-{{.}}" class="item" data-token="{{.TokenAuth}}" data-value="{{.}}">{{.Title}}</div>
+								{{end}}
 							</div>
-							<input class="fake" type="password">
-							<div class="inline field {{if .Err_Auth}}error{{end}}">
-								<label for="auth_password">{{.i18n.Tr "password"}}</label>
-								<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}">
+						</div>
+					</div>
+					<div class="inline field {{if .Err_Auth}}error{{end}}">
+						<label for="auth_username">{{.i18n.Tr "username"}}</label>
+						<input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}>
+					</div>
+					<input class="fake" type="password">
+					<div class="inline field {{if .Err_Auth}}error{{end}}">
+						<label for="auth_password">{{.i18n.Tr "password"}}</label>
+						<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}">
+					</div>
+					<div class="inline field {{if .Err_Auth}}error{{end}}">
+						<label for="auth_token">{{.i18n.Tr "access_token"}}</label>
+						<input id="auth_token" name="auth_token" value="{{.auth_token}}" {{if not .auth_token}}data-need-clear="true"{{end}}>
+					</div>
+
+					<div class="inline field">
+						<label>{{.i18n.Tr "repo.migrate_options"}}</label>
+						<div class="ui checkbox">
+							{{if .DisableMirrors}}
+								<input id="mirror" name="mirror" type="checkbox" readonly>
+								<label>{{.i18n.Tr "repo.migrate_options_mirror_disabled"}}</label>
+							{{else}}
+								<input id="mirror" name="mirror" type="checkbox" {{if .mirror}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.migrate_options_mirror_helper" | Safe}}</label>
+							{{end}}
+						</div>
+					</div>
+
+					<span class="help">{{.i18n.Tr "repo.migrate.migrate_items_options"}}</span>
+					<div id="migrate_items">
+						<div class="inline field">
+							<label>{{.i18n.Tr "repo.migrate_items"}}</label>
+							<div class="ui checkbox">
+								<input name="wiki" type="checkbox" {{if .wiki}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.migrate_items_wiki" | Safe}}</label>
+							</div>
+							<div class="ui checkbox">
+								<input name="milestones" type="checkbox" {{if .milestones}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label>
+							</div>
+						</div>
+						<div class="inline field">
+							<label></label>
+							<div class="ui checkbox">
+								<input name="labels" type="checkbox" {{if .labels}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label>
+							</div>
+							<div class="ui checkbox">
+								<input name="issues" type="checkbox" {{if .issues}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label>
+							</div>
+						</div>
+						<div class="inline field">
+							<label></label>
+							<div class="ui checkbox">
+								<input name="pull_requests" type="checkbox" {{if .pull_requests}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.migrate_items_pullrequests" | Safe}}</label>
+							</div>
+							<div class="ui checkbox">
+								<input name="releases" type="checkbox" {{if .releases}}checked{{end}}>
+								<label>{{.i18n.Tr "repo.migrate_items_releases" | Safe}}</label>
 							</div>
 						</div>
 					</div>
@@ -78,53 +137,6 @@
 							{{end}}
 						</div>
 					</div>
-					<div class="inline field">
-						<label>{{.i18n.Tr "repo.migrate_type"}}</label>
-						<div class="ui checkbox">
-							{{if .DisableMirrors}}
-								<input id="mirror" name="mirror" type="checkbox" readonly>
-								<label>{{.i18n.Tr "repo.migrate_type_helper_disabled"}}</label>
-							{{else}}
-								<input id="mirror" name="mirror" type="checkbox" {{if .mirror}}checked{{end}}>
-								<label>{{.i18n.Tr "repo.migrate_type_helper" | Safe}}</label>
-							{{end}}
-						</div>
-					</div>
-					<div id="migrate_items" class="ui field">
-						<div class="inline field">
-							<label>{{.i18n.Tr "repo.migrate_items"}}</label>
-							<div class="ui checkbox">
-								<input name="wiki" type="checkbox" {{if .wiki}}checked{{end}}>
-								<label>{{.i18n.Tr "repo.migrate_items_wiki" | Safe}}</label>
-							</div>
-							<div class="ui checkbox">
-								<input name="milestones" type="checkbox" {{if .milestones}}checked{{end}}>
-								<label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label>
-							</div>
-						</div>
-						<div class="inline field">
-							<label></label>
-							<div class="ui checkbox">
-								<input name="labels" type="checkbox" {{if .labels}}checked{{end}}>
-								<label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label>
-							</div>
-							<div class="ui checkbox">
-								<input name="issues" type="checkbox" {{if .issues}}checked{{end}}>
-								<label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label>
-							</div>
-						</div>
-						<div class="inline field">
-							<label></label>
-							<div class="ui checkbox">
-								<input name="pull_requests" type="checkbox" {{if .pull_requests}}checked{{end}}>
-								<label>{{.i18n.Tr "repo.migrate_items_pullrequests" | Safe}}</label>
-							</div>
-							<div class="ui checkbox">
-								<input name="releases" type="checkbox" {{if .releases}}checked{{end}}>
-								<label>{{.i18n.Tr "repo.migrate_items_releases" | Safe}}</label>
-							</div>
-						</div>
-					</div>
 					<div class="inline field {{if .Err_Description}}error{{end}}">
 						<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
 						<textarea id="description" name="description">{{.description}}</textarea>
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index d9c8aeb87d..f1e0b0080d 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -13446,6 +13446,10 @@
           "type": "string",
           "x-go-name": "AuthPassword"
         },
+        "auth_token": {
+          "type": "string",
+          "x-go-name": "AuthToken"
+        },
         "auth_username": {
           "type": "string",
           "x-go-name": "AuthUsername"
@@ -13490,6 +13494,11 @@
           "type": "string",
           "x-go-name": "RepoName"
         },
+        "service": {
+          "type": "integer",
+          "format": "int64",
+          "x-go-name": "Service"
+        },
         "uid": {
           "type": "integer",
           "format": "int64",
diff --git a/web_src/js/features/migration.js b/web_src/js/features/migration.js
new file mode 100644
index 0000000000..e4c306307f
--- /dev/null
+++ b/web_src/js/features/migration.js
@@ -0,0 +1,53 @@
+const $service = $('#service_type');
+const $user = $('#auth_username');
+const $pass = $('#auth_password');
+const $token = $('#auth_token');
+const $items = $('#migrate_items').find('.field');
+
+export default function initMigration() {
+  checkAuth();
+
+  $service.on('change', checkAuth);
+  $user.on('keyup', () => {checkItems(false)});
+  $pass.on('keyup', () => {checkItems(false)});
+  $token.on('keyup', () => {checkItems(true)});
+
+  const $cloneAddr = $('#clone_addr');
+  $cloneAddr.on('change', () => {
+    const $repoName = $('#repo_name');
+    if ($cloneAddr.val().length > 0 && $repoName.val().length === 0) { // Only modify if repo_name input is blank
+      $repoName.val($cloneAddr.val().match(/^(.*\/)?((.+?)(\.git)?)$/)[3]);
+    }
+  });
+}
+
+function checkAuth() {
+  const serviceType = $service.val();
+  const tokenAuth = $(`#service-${serviceType}`).data('token');
+
+  if (tokenAuth) {
+    $user.parent().addClass('disabled');
+    $pass.parent().addClass('disabled');
+    $token.parent().removeClass('disabled');
+  } else {
+    $user.parent().removeClass('disabled');
+    $pass.parent().removeClass('disabled');
+    $token.parent().addClass('disabled');
+  }
+
+  checkItems(tokenAuth);
+}
+
+function checkItems(tokenAuth) {
+  let enableItems;
+  if (tokenAuth) {
+    enableItems = $token.val() !== '';
+  } else {
+    enableItems = $user.val() !== '' || $pass.val() !== '';
+  }
+  if (enableItems && $service.val() > 1) {
+    $items.removeClass('disabled');
+  } else {
+    $items.addClass('disabled');
+  }
+}
diff --git a/web_src/js/index.js b/web_src/js/index.js
index a1b5035764..810493a7d6 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -8,6 +8,7 @@ import {htmlEscape} from 'escape-goat';
 import 'jquery.are-you-sure';
 import './vendor/semanticdropdown.js';
 
+import initMigration from './features/migration.js';
 import initContextPopups from './features/contextpopup.js';
 import initGitGraph from './features/gitgraph.js';
 import initClipboard from './features/clipboard.js';
@@ -1155,25 +1156,6 @@ async function initRepository() {
   }
 }
 
-function initMigration() {
-  const toggleMigrations = function () {
-    const authUserName = $('#auth_username').val();
-    const cloneAddr = $('#clone_addr').val();
-    if (!$('#mirror').is(':checked') && (authUserName && authUserName.length > 0) &&
-        (cloneAddr !== undefined && (cloneAddr.startsWith('https://github.com') || cloneAddr.startsWith('http://github.com') || cloneAddr.startsWith('http://gitlab.com') || cloneAddr.startsWith('https://gitlab.com')))) {
-      $('#migrate_items').show();
-    } else {
-      $('#migrate_items').hide();
-    }
-  };
-
-  toggleMigrations();
-
-  $('#clone_addr').on('input', toggleMigrations);
-  $('#auth_username').on('input', toggleMigrations);
-  $('#mirror').on('change', toggleMigrations);
-}
-
 function initPullRequestReview() {
   $('.show-outdated').on('click', function (e) {
     e.preventDefault();
@@ -2477,14 +2459,6 @@ $(document).ready(async () => {
     }
   }
 
-  const $cloneAddr = $('#clone_addr');
-  $cloneAddr.on('change', () => {
-    const $repoName = $('#repo_name');
-    if ($cloneAddr.val().length > 0 && $repoName.val().length === 0) { // Only modify if repo_name input is blank
-      $repoName.val($cloneAddr.val().match(/^(.*\/)?((.+?)(\.git)?)$/)[3]);
-    }
-  });
-
   // parallel init of async loaded features
   await Promise.all([
     attachTribute(document.querySelectorAll('#content, .emoji-input')),
diff --git a/web_src/less/_form.less b/web_src/less/_form.less
index 07c7cdfc8b..f8123b8f9b 100644
--- a/web_src/less/_form.less
+++ b/web_src/less/_form.less
@@ -180,6 +180,11 @@
         text-align: center;
       }
 
+      .selection.dropdown {
+        vertical-align: middle;
+        width: 50% !important;
+      }
+
       @media only screen and (max-width: 768px) {
         label,
         input,