[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-15":3},{"id":4,"title":5,"title_en":6,"abstract":7,"abstract_en":8,"content":9,"content_en":10,"category":11,"banner_id":12,"banner_path":13,"tags":14,"is_recommend":18,"prev_article":19,"next_article":23,"created_at":27},15,"Vue3 + Go 实现第三方 OAuth 登录（GitHub , Microsoft）指南","Vue3 + Go Implement Third-Party OAuth Sign-in (GitHub, Microsoft) Complete Guide","在现代 Web 应用中，第三方登录已经成为用户体验的重要组成部分。本文将详细介绍如何在 Vue3 前端和 Go 后端项目中实现 GitHub 和 Microsoft OAuth 登录功能。","In modern web applications, third-party logins have become an important part of the user experience. This article details how to implement GitHub and Microsoft OAuth sign-in capabilities in Vue3 frontend and Go backend projects.","# Vue3 + Go 实现第三方 OAuth 登录（GitHub &amp; Microsoft）完整指南\n\n> 在现代 Web 应用中，第三方登录已经成为用户体验的重要组成部分。本文将详细介绍如何在 Vue3 前端和 Go 后端项目中实现 GitHub 和 Microsoft OAuth 登录功能。\n\n## 项目架构\n\n- **前端**: Vue3 + TypeScript + Arco Design\n- **后端**: Go + Gin + GORM\n- **数据库**: MySQL\n- **OAuth 提供商**: GitHub、Microsoft\n\n## 一、OAuth 登录流程原理\n\n### 标准 OAuth 2.0 流程\n\n```\n用户 > 前端 > 后端重定向接口 > OAuth授权页面 > 用户授权 > \nOAuth回调后端 > 后端获取用户信息 > 后端生成JWT > \n后端重定向前端 > 前端保存token > 跳转首页\n```\n\n**详细步骤：**\n\n1. 用户点击第三方登录按钮\n2. 前端跳转到后端重定向接口\n3. 后端重定向到 OAuth 提供商授权页面\n4. 用户在 OAuth 页面授权\n5. OAuth 提供商回调后端并返回授权码\n6. 后端用授权码换取 access_token\n7. 后端用 access_token 获取用户信息\n8. 后端创建\u002F更新用户，生成 JWT token\n9. 后端重定向到前端回调页面（携带 token）\n10. 前端保存 token，跳转到目标页面\n\n## 二、后端实现\n\n### 1. 数据库模型设计\n\n```go\ntype UserModel struct {\n    ID           uint   `gorm:\"primaryKey\"`\n    Username     string\n    Nickname     string\n    Email        string\n    Avatar       string\n    Password     string\n    \n    \u002F\u002F 第三方登录ID（统一使用string类型兼容所有平台）\n    GithubID     string `gorm:\"index\"`\n    MicrosoftID  string `gorm:\"index\"`\n    \n    Role         int    \u002F\u002F 用户角色\n    SignStatus   int    \u002F\u002F 登录方式标识\n    IP           string\n    Addr         string\n    CreatedAt    time.Time\n    UpdatedAt    time.Time\n}\n```\n\n### 2. GitHub OAuth 实现\n\n#### OAuth 配置\n\n```go\ntype GitHubOAuth struct{}\n\nfunc (g GitHubOAuth) Config() *oauth2.Config {\n    return &amp;oauth2.Config{\n        ClientID:     config.GitHub.ClientID,\n        ClientSecret: config.GitHub.ClientSecret,\n        RedirectURL:  config.GitHub.RedirectURL,\n        Scopes:       []string{\"user:email\"},\n        Endpoint:     github.Endpoint,\n    }\n}\n```\n\n#### 用户信息结构\n\n```go\ntype GithubUserResponse struct {\n    ID        int    `json:\"id\"`\n    Login     string `json:\"login\"`\n    Name      string `json:\"name\"`\n    Email     string `json:\"email\"`\n    AvatarURL string `json:\"avatar_url\"`\n}\n```\n\n#### 重定向接口\n\n```go\nfunc GithubOAuthRedirectView(c *gin.Context) {\n    config := GitHubOAuth{}.Config()\n    url := config.AuthCodeURL(\"state\", oauth2.AccessTypeOnline)\n    c.Redirect(http.StatusFound, url)\n}\n```\n\n#### 回调处理\n\n```go\nfunc GithubOAuthCallbackView(c *gin.Context) {\n    \u002F\u002F 获取授权码\n    code := c.Query(\"code\")\n    if code == \"\" {\n        c.JSON(400, gin.H{\"error\": \"认证失败\"})\n        return\n    }\n    \n    \u002F\u002F 交换access_token\n    config := GitHubOAuth{}.Config()\n    token, err := config.Exchange(c, code)\n    if err != nil {\n        c.JSON(400, gin.H{\"error\": \"认证失败\"})\n        return\n    }\n    \n    \u002F\u002F 获取用户信息\n    client := config.Client(c, token)\n    resp, err := client.Get(\"https:\u002F\u002Fapi.github.com\u002Fuser\")\n    if err != nil {\n        c.JSON(400, gin.H{\"error\": \"获取用户信息失败\"})\n        return\n    }\n    defer resp.Body.Close()\n    \n    body, _ := io.ReadAll(resp.Body)\n    var githubUser GithubUserResponse\n    if err := json.Unmarshal(body, &amp;githubUser); err != nil {\n        c.JSON(400, gin.H{\"error\": \"用户信息解析失败\"})\n        return\n    }\n    \n    \u002F\u002F 获取用户IP和地理位置\n    userIP := c.ClientIP()\n    \u002F\u002F 地理位置解析逻辑...\n    \n    \u002F\u002F 查找或创建用户\n    var user UserModel\n    err = db.Where(\"github_id = ?\", strconv.Itoa(githubUser.ID)).First(&amp;user).Error\n    if err != nil {\n        \u002F\u002F 创建新用户\n        user = UserModel{\n            Username:   githubUser.Login,\n            Nickname:   githubUser.Name,\n            Avatar:     githubUser.AvatarURL,\n            GithubID:   strconv.Itoa(githubUser.ID),\n            Email:      githubUser.Email,\n            IP:         userIP,\n            Role:       2, \u002F\u002F 普通用户\n            SignStatus: 1, \u002F\u002F GitHub登录\n        }\n        \n        if err := db.Create(&amp;user).Error; err != nil {\n            c.JSON(500, gin.H{\"error\": \"创建用户失败\"})\n            return\n        }\n    }\n    \n    \u002F\u002F 生成JWT token\n    accessToken := generateAccessToken(user)\n    refreshToken := generateRefreshToken(user)\n    \n    \u002F\u002F 重定向到前端回调页面\n    callbackURL := fmt.Sprintf(\"http:\u002F\u002Flocalhost:3000\u002Flogin\u002Fgithub\u002Fcallback?access_token=%s&refresh_token=%s\", \n        accessToken, refreshToken)\n    c.Redirect(http.StatusFound, callbackURL)\n}\n```\n\n### 3. Microsoft OAuth 实现\n\n#### OAuth 配置\n\n```go\ntype MicrosoftOAuth struct{}\n\nfunc (m MicrosoftOAuth) Config() *oauth2.Config {\n    return &amp;oauth2.Config{\n        ClientID:     config.Microsoft.ClientID,\n        ClientSecret: config.Microsoft.ClientSecret,\n        RedirectURL:  config.Microsoft.RedirectURL,\n        Scopes:       []string{\"https:\u002F\u002Fgraph.microsoft.com\u002FUser.Read\"},\n        Endpoint: oauth2.Endpoint{\n            AuthURL:  \"https:\u002F\u002Flogin.microsoftonline.com\u002Fcommon\u002Foauth2\u002Fv2.0\u002Fauthorize\",\n            TokenURL: \"https:\u002F\u002Flogin.microsoftonline.com\u002Fcommon\u002Foauth2\u002Fv2.0\u002Ftoken\",\n        },\n    }\n}\n```\n\n#### 用户信息结构\n\n```go\ntype MicrosoftUserResponse struct {\n    ID                string `json:\"id\"`\n    DisplayName       string `json:\"displayName\"`\n    GivenName         string `json:\"givenName\"`\n    UserPrincipalName string `json:\"userPrincipalName\"`\n    Mail              string `json:\"mail\"`\n}\n```\n\n#### 回调处理（关键差异）\n\n```go\nfunc MicrosoftOAuthCallbackView(c *gin.Context) {\n    \u002F\u002F 获取授权码（注意：使用Query而不是Param）\n    code := c.Query(\"code\")\n    if code == \"\" {\n        c.JSON(400, gin.H{\"error\": \"认证失败\"})\n        return\n    }\n    \n    \u002F\u002F 交换token\n    config := MicrosoftOAuth{}.Config()\n    token, err := config.Exchange(c, code)\n    if err != nil {\n        c.JSON(400, gin.H{\"error\": \"认证失败\"})\n        return\n    }\n    \n    \u002F\u002F 获取用户信息（使用Microsoft Graph API）\n    client := config.Client(c, token)\n    resp, err := client.Get(\"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\u002Fme\")\n    if err != nil {\n        c.JSON(400, gin.H{\"error\": \"获取用户信息失败\"})\n        return\n    }\n    defer resp.Body.Close()\n    \n    body, _ := io.ReadAll(resp.Body)\n    var microsoftUser MicrosoftUserResponse\n    if err := json.Unmarshal(body, µsoftUser); err != nil {\n        c.JSON(400, gin.H{\"error\": \"用户信息解析失败\"})\n        return\n    }\n    \n    \u002F\u002F 查找或创建用户\n    var user UserModel\n    err = db.Where(\"microsoft_id = ?\", microsoftUser.ID).First(&amp;user).Error\n    if err != nil {\n        user = UserModel{\n            Username:    microsoftUser.UserPrincipalName,\n            Nickname:    microsoftUser.DisplayName,\n            MicrosoftID: microsoftUser.ID,\n            Email:       microsoftUser.Mail,\n            Role:        2, \u002F\u002F 普通用户\n            SignStatus:  2, \u002F\u002F Microsoft登录\n        }\n        \n        if err := db.Create(&amp;user).Error; err != nil {\n            c.JSON(500, gin.H{\"error\": \"创建用户失败\"})\n            return\n        }\n    }\n    \n    \u002F\u002F 生成JWT并重定向\n    accessToken := generateAccessToken(user)\n    refreshToken := generateRefreshToken(user)\n    \n    callbackURL := fmt.Sprintf(\"http:\u002F\u002Flocalhost:3000\u002Flogin\u002Fmicrosoft\u002Fcallback?access_token=%s&amp;refresh_token=%s\", \n        accessToken, refreshToken)\n    c.Redirect(http.StatusFound, callbackURL)\n}\n```\n\n### 4. 路由配置\n\n```go\nfunc InitOAuthRoutes(router *gin.RouterGroup) {\n    \u002F\u002F GitHub OAuth\n    router.GET(\"auth\u002Fgithub\", GithubOAuthRedirectView)\n    router.GET(\"auth\u002Fgithub\u002Fcallback\", GithubOAuthCallbackView)\n    \n    \u002F\u002F Microsoft OAuth\n    router.GET(\"auth\u002Fmicrosoft\", MicrosoftOAuthRedirectView)\n    router.GET(\"auth\u002Fmicrosoft\u002Fcallback\", MicrosoftOAuthCallbackView)\n}\n```\n\n## 三、前端实现\n\n### 1. 登录表单组件\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\"login-form\">\n        \u003Cdiv class=\"title\">用户登录\u003C\u002Fdiv>\n\n                    \u003Ctemplate>\u003C\u002Ftemplate>\n\n                    \u003Ctemplate>\u003C\u002Ftemplate>\n\n            登录\n\n        \u003Cdiv class=\"oauth-login\">\n            \u003Cdiv class=\"label\">第三方登录\u003C\u002Fdiv>\n            \u003Cdiv class=\"icons\">\n                \u003Ca>\n                    \u003Cimg src=\"\u002Fimages\u002Fgithub.png\" alt=\"GitHub登录\">\n                \u003C\u002Fa>\n                \u003Ca>\n                    \u003Cimg src=\"\u002Fimages\u002Fmicrosoft.svg\" alt=\"Microsoft登录\">\n                \u003C\u002Fa>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\n\n\u003Cstyle lang=\"scss\">\n.oauth-login {\n    margin-top: 20px;\n    \n    .label {\n        text-align: center;\n        color: #666;\n        margin-bottom: 15px;\n        position: relative;\n        \n        &::before, &::after {\n            content: \"\";\n            position: absolute;\n            top: 50%;\n            width: 30%;\n            height: 1px;\n            background-color: #ddd;\n        }\n        \n        &::before { left: 0; }\n        &::after { right: 0; }\n    }\n    \n    .icons {\n        display: flex;\n        justify-content: center;\n        gap: 20px;\n        \n        img {\n            width: 40px;\n            height: 40px;\n            cursor: pointer;\n            transition: transform 0.2s;\n            \n            &:hover {\n                transform: scale(1.1);\n            }\n        }\n    }\n}\n\u003C\u002Fstyle>\n```\n\n### 2. GitHub 回调页面\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\"oauth-callback\">\n        \u003Cdiv class=\"loading-container\">\n            \n            \u003Cp>正在处理 GitHub 登录...\u003C\u002Fp>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\n\n\u003Cstyle lang=\"scss\">\n.oauth-callback {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    height: 100vh;\n    \n    .loading-container {\n        text-align: center;\n        \n        p {\n            margin-top: 16px;\n            color: #666;\n        }\n    }\n}\n\u003C\u002Fstyle>\n```\n\n### 3. Microsoft 回调页面\n\nMicrosoft 回调页面与 GitHub 类似，只需要修改相关的文案和标识符：\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\"oauth-callback\">\n        \u003Cdiv class=\"loading-container\">\n            \n            \u003Cp>正在处理 Microsoft 登录...\u003C\u002Fp>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\n```\n\n### 4. 路由配置\n\n```typescript\nimport { createRouter, createWebHistory } from 'vue-router'\n\nconst router = createRouter({\n    history: createWebHistory(),\n    routes: [\n        {\n            path: \"\u002Flogin\",\n            name: \"login\",\n            component: () =&gt; import('@\u002Fviews\u002Flogin\u002Findex.vue')\n        },\n        {\n            path: \"\u002Flogin\u002Fgithub\u002Fcallback\",\n            name: \"github_callback\",\n            component: () =&gt; import('@\u002Fviews\u002Flogin\u002Fgithub-callback.vue')\n        },\n        {\n            path: \"\u002Flogin\u002Fmicrosoft\u002Fcallback\",\n            name: \"microsoft_callback\",\n            component: () =&gt; import('@\u002Fviews\u002Flogin\u002Fmicrosoft-callback.vue')\n        },\n        \u002F\u002F 其他路由...\n    ]\n})\n\nexport default router\n```\n\n### 5. 用户状态管理\n\n```typescript\nimport { defineStore } from 'pinia'\n\ninterface UserInfo {\n    access_token: string\n    refresh_token: string\n    user_id: number\n    nickname: string\n    avatar: string\n    role: number\n}\n\nexport const useUserStore = defineStore('user', {\n    state(): { userInfo: UserInfo } {\n        return {\n            userInfo: {\n                access_token: \"\",\n                refresh_token: \"\",\n                user_id: 0,\n                nickname: \"\",\n                avatar: \"\",\n                role: 0\n            }\n        }\n    },\n    \n    actions: {\n        setToken(accessToken: string, refreshToken: string) {\n            this.userInfo.access_token = accessToken\n            this.userInfo.refresh_token = refreshToken\n            \n            \u002F\u002F 解析JWT获取用户信息\n            const payload = this.parseJWT(accessToken)\n            if (payload) {\n                this.userInfo.user_id = payload.user_id\n                this.userInfo.nickname = payload.nickname\n                this.userInfo.avatar = payload.avatar\n                this.userInfo.role = payload.role\n            }\n            \n            \u002F\u002F 持久化存储\n            localStorage.setItem(\"userInfo\", JSON.stringify(this.userInfo))\n        },\n        \n        parseJWT(token: string) {\n            try {\n                const base64Url = token.split('.')[1]\n                const base64 = base64Url.replace(\u002F-\u002Fg, '+').replace(\u002F_\u002Fg, '\u002F')\n                const jsonPayload = decodeURIComponent(\n                    atob(base64).split('').map(c =&gt; \n                        '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)\n                    ).join('')\n                )\n                return JSON.parse(jsonPayload)\n            } catch (error) {\n                console.error('JWT解析失败:', error)\n                return null\n            }\n        },\n        \n        logout() {\n            this.userInfo = {\n                access_token: \"\",\n                refresh_token: \"\",\n                user_id: 0,\n                nickname: \"\",\n                avatar: \"\",\n                role: 0\n            }\n            localStorage.removeItem(\"userInfo\")\n        }\n    },\n    \n    getters: {\n        isLoggedIn(): boolean {\n            return !!this.userInfo.access_token &amp;&amp; this.userInfo.user_id &gt; 0\n        }\n    }\n})\n```\n\n## 四、OAuth 应用配置\n\n### 1. GitHub OAuth 应用\n\n1. 访问 [GitHub Developer Settings](https:\u002F\u002Fgithub.com\u002Fsettings\u002Fdevelopers)\n2. 点击 \"New OAuth App\"\n3. 填写应用信息：\n   - **Application name**: 你的应用名称\n   - **Homepage URL**: `http:\u002F\u002Flocalhost:3000`（开发环境）\n   - **Authorization callback URL**: `http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fauth\u002Fgithub\u002Fcallback`\n4. 获取 Client ID 和 Client Secret\n\n### 2. Microsoft OAuth 应用\n\n1. 访问 [Azure Portal](https:\u002F\u002Fportal.azure.com)\n2. 进入 **Azure Active Directory** → **应用注册**\n3. 点击 \"新注册\"\n4. 填写应用信息：\n   - **名称**: 你的应用名称\n   - **支持的帐户类型**: 任何组织目录中的帐户和个人 Microsoft 帐户\n5. 配置身份验证：\n   - **平台**: Web\n   - **重定向 URI**: `http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fauth\u002Fmicrosoft\u002Fcallback`\n   - **令牌配置**: 勾选 \"ID 令牌\" 和 \"访问令牌\"\n6. 生成客户端密钥\n\n## 五、常见问题与解决方案\n\n### 1. redirect_uri 不匹配\n\n**错误信息**: `The provided value for the input parameter 'redirect_uri' is not valid`\n\n**解决方案**: \n- 确保 OAuth 应用配置中的回调 URL 与后端代码完全一致\n- 检查协议（http\u002Fhttps）、域名、端口、路径是否匹配\n- 注意 `localhost` 和 `127.0.0.1` 的区别\n\n### 2. scope 权限配置\n\n**GitHub**: \n- 基本信息：`[]string{\"user:email\"}`\n- 更多权限：`[]string{\"user\", \"user:email\"}`\n\n**Microsoft**: \n- Graph API：`[]string{\"https:\u002F\u002Fgraph.microsoft.com\u002FUser.Read\"}`\n- OpenID Connect：`[]string{\"openid\", \"profile\", \"email\"}`\n\n### 3. JSON 解析失败\n\n确保结构体的 JSON 标签与 API 返回字段名匹配：\n\n```go\n\u002F\u002F GitHub API 返回字段\ntype GithubUser struct {\n    ID        int    `json:\"id\"`        \u002F\u002F 正确\n    AvatarURL string `json:\"avatar_url\"` \u002F\u002F 注意下划线\n}\n\n\u002F\u002F Microsoft Graph API 返回字段  \ntype MicrosoftUser struct {\n    ID          string `json:\"id\"`          \u002F\u002F 正确\n    DisplayName string `json:\"displayName\"` \u002F\u002F 注意驼峰命名\n}\n```\n\n### 4. 获取授权码方式\n\n**正确方式**:\n```go\ncode := c.Query(\"code\") \u002F\u002F 使用 Query 获取 URL 参数\n```\n\n**错误方式**:\n```go\ncode := c.Param(\"code\") \u002F\u002F 错误：Param 用于路径参数\n```\n\n### 5. 频繁请求限制\n\n开发阶段避免频繁测试：\n- 清除浏览器缓存和 Cookie\n- 在 OAuth 提供商处撤销应用授权\n- 使用不同浏览器或无痕模式\n- 等待一段时间后重试\n\n### 6. 前端回调页面问题\n\n确保后端重定向到前端页面而不是返回 JSON：\n\n```go\n\u002F\u002F 正确：重定向到前端\ncallbackURL := fmt.Sprintf(\"http:\u002F\u002Flocalhost:3000\u002Flogin\u002Fgithub\u002Fcallback?access_token=%s\", token)\nc.Redirect(http.StatusFound, callbackURL)\n\n\u002F\u002F 错误：返回 JSON\nc.JSON(200, gin.H{\"access_token\": token})\n```\n\n## 六、安全考虑\n\n### 1. HTTPS 使用\n\n生产环境必须使用 HTTPS：\n- OAuth 提供商要求 HTTPS 回调 URL\n- 保护 token 传输安全\n- 防止中间人攻击\n\n### 2. State 参数验证\n\n```go\n\u002F\u002F 生成随机 state\nstate := generateRandomString(32)\n\u002F\u002F 存储到 session 或 Redis\nsession.Set(\"oauth_state\", state)\n\n\u002F\u002F 回调时验证\ncallbackState := c.Query(\"state\")\nsessionState := session.Get(\"oauth_state\")\nif callbackState != sessionState {\n    c.JSON(400, gin.H{\"error\": \"状态验证失败\"})\n    return\n}\n```\n\n### 3. Token 安全\n\n- 设置合理的 JWT 过期时间（如 2 小时）\n- 实现 Refresh Token 机制\n- 敏感信息不存储在 JWT payload 中\n- 使用强密钥签名 JWT\n\n### 4. 输入验证\n\n```go\n\u002F\u002F 验证必要字段\nif user.ID == \"\" || user.Email == \"\" {\n    c.JSON(400, gin.H{\"error\": \"用户信息不完整\"})\n    return\n}\n\n\u002F\u002F 邮箱格式验证\nif !isValidEmail(user.Email) {\n    c.JSON(400, gin.H{\"error\": \"邮箱格式无效\"})\n    return\n}\n```\n\n### 5. 错误处理\n\n不要在错误信息中暴露敏感信息：\n\n```go\n\u002F\u002F 好的做法\nc.JSON(400, gin.H{\"error\": \"认证失败\"})\n\n\u002F\u002F 避免暴露详细错误\nc.JSON(400, gin.H{\"error\": fmt.Sprintf(\"数据库错误: %v\", err)})\n```\n\n## 七、部署注意事项\n\n### 1. 环境变量配置\n\n```bash\n# GitHub OAuth\nGITHUB_CLIENT_ID=your_github_client_id\nGITHUB_CLIENT_SECRET=your_github_client_secret\nGITHUB_REDIRECT_URL=https:\u002F\u002Fyourdomain.com\u002Fapi\u002Fauth\u002Fgithub\u002Fcallback\n\n# Microsoft OAuth  \nMICROSOFT_CLIENT_ID=your_microsoft_client_id\nMICROSOFT_CLIENT_SECRET=your_microsoft_client_secret\nMICROSOFT_REDIRECT_URL=https:\u002F\u002Fyourdomain.com\u002Fapi\u002Fauth\u002Fmicrosoft\u002Fcallback\n\n# JWT 密钥\nJWT_SECRET=your_jwt_secret_key\n```\n\n### 2. 域名配置\n\n生产环境需要更新：\n- OAuth 应用的回调 URL\n- 前端环境变量中的 API 地址\n- 后端重定向的前端地址\n\n### 3. CORS 配置\n\n```go\nfunc CORSMiddleware() gin.HandlerFunc {\n    return gin.HandlerFunc(func(c *gin.Context) {\n        c.Header(\"Access-Control-Allow-Origin\", \"https:\u002F\u002Fyourdomain.com\")\n        c.Header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\")\n        c.Header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\")\n        \n        if c.Request.Method == \"OPTIONS\" {\n            c.AbortWithStatus(204)\n            return\n        }\n        \n        c.Next()\n    })\n}\n```\n\n## 八、总结\n\n本文详细介绍了 Vue3 + Go 实现第三方 OAuth 登录的完整方案，包括：\n\n**核心要点**：\n1. **理解 OAuth 流程**：掌握授权码模式的完整流程\n2. **后端实现**：正确处理重定向、回调、用户信息获取和 JWT 生成\n3. **前端实现**：简洁的登录界面和回调页面处理\n4. **配置管理**：OAuth 应用的正确配置和环境变量管理\n5. **安全考虑**：State 验证、HTTPS、Token 安全等\n6. **错误处理**：完善的错误处理和用户友好的提示\n\n**技术收获**：\n- OAuth 2.0 标准流程的实际应用\n- 前后端分离架构下的认证方案\n- JWT Token 的生成和验证\n- Vue3 Composition API 的实践\n- Go Gin 框架的 HTTP 处理\n\n通过这套完整的实现方案，用户可以便捷地使用 GitHub 或 Microsoft 账号登录应用，大大提升用户体验。在实际项目中，还可以根据需要扩展支持更多 OAuth 提供商，如 Google、微信、QQ 等。\n\n## 参考资源\n\n- [OAuth 2.0 RFC 规范](https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc6749)\n- [GitHub OAuth 文档](https:\u002F\u002Fdocs.github.com\u002Fen\u002Fdevelopers\u002Fapps\u002Fbuilding-oauth-apps)\n- [Microsoft Graph API 文档](https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fgraph\u002F)\n- [Vue.js 官方文档](https:\u002F\u002Fvuejs.org\u002F)\n- [Gin Web 框架文档](https:\u002F\u002Fgin-gonic.com\u002F)","# Vue3 + Go Complete guide to implementing third-party OAuth sign-in (GitHub & Microsoft).\n\n> In modern web applications, third-party sign-ins have become an important part of the user experience. This article details how to implement GitHub and Microsoft OAuth sign-in capabilities in Vue3 frontend and Go backend projects.\n\n## Project Architecture\n\n- **Frontend**: Vue3 + TypeScript + Arco Design\n- **Backend**: Go + Gin + GORM\n- **Database**: MySQL\n- **OAuth providers**: GitHub, Microsoft\n\n## 1. Principle of OAuth login process\n\n### Standard OAuth 2.0 flow\n\n```\nUser > Frontend > Backend Redirect Interface > OAuth Authorization Page > User Authorization > \nOAuth callbacks > backends obtain user information > backends generate JWT > \nBackend redirects to frontend > frontend saves token > jumps to the homepage\n```\n\n**Detailed Steps:**\n\n1. The user clicks on the third-party login button\n2. Jump to the backend redirect interface\n3. The backend redirects to the OAuth provider authorization page\n4. User authorization on the OAuth page\n5. The OAuth provider calls back the backend and returns the authorization code\n6. The backend exchanges the authorization code for access_token\n7. The backend uses access_token to obtain user information\n8. Create\u002Fupdate users on the backend and generate JWT tokens\n9. Backend redirects to frontend callback page (with token)\n10. Save the token on the frontend and jump to the target page\n\n## 2. Back-end implementation\n\n### 1. Database model design\n\n```go\ntype UserModel struct {\n    ID           uint   `gorm:\"primaryKey\"`\n    Username     string\n    Nickname     string\n    Email        string\n    Avatar       string\n    Password     string\n    \n    Third-party login ID (uniformly using string type is compatible with all platforms)\n    GithubID     string `gorm:\"index\"`\n    MicrosoftID  string `gorm:\"index\"`\n    \n    Role int \u002F\u002F User role\n    SignStatus int \u002F\u002F Login method identifier\n    IP           string\n    Addr         string\n    CreatedAt    time. Time\n    UpdatedAt    time. Time\n}\n```\n\n### 2. GitHub OAuth implementation\n\n#### OAuth configuration\n\n```go\ntype GitHubOAuth struct{}\n\nfunc (g GitHubOAuth) Config() *oauth2. Config {\n    return &oauth2. Config{\n        ClientID:     config. GitHub.ClientID,\n        ClientSecret: config. GitHub.ClientSecret,\n        RedirectURL:  config. GitHub.RedirectURL,\n        Scopes:       []string{\"user:email\"},\n        Endpoint:     github. Endpoint,\n    }\n}\n```\n\n#### User Information Structure\n\n```go\ntype GithubUserResponse struct {\n    ID        int    `json:\"id\"`\n    Login     string `json:\"login\"`\n    Name      string `json:\"name\"`\n    Email     string `json:\"email\"`\n    AvatarURL string `json:\"avatar_url\"`\n}\n```\n\n#### Redirect interface\n\n```go\nfunc GithubOAuthRedirectView(c *gin. Context) {\n    config := GitHubOAuth{}. Config()\n    url := config. AuthCodeURL(\"state\", oauth2. AccessTypeOnline)\n    c.Redirect(http. StatusFound, url)\n}\n```\n\n#### Callback handling\n\n```go\nfunc GithubOAuthCallbackView(c *gin. Context) {\n    Get an authorization code\n    code := c.Query(\"code\")\n    if code == \"\" {\n        c.JSON(400, gin. H{\"error\": \"authentication failed\"})\n        return\n    }\n    \n    Exchange access_token\n    config := GitHubOAuth{}. Config()\n    token, err := config. Exchange(c, code)\n    if err != nil {\n        c.JSON(400, gin. H{\"error\": \"authentication failed\"})\n        return\n    }\n    \n    Get user information\n    client := config. Client(c, token)\n    resp, err := client. Get(\"https:\u002F\u002Fapi.github.com\u002Fuser\")\n    if err != nil {\n        c.JSON(400, gin. H{\"error\": \"Failed to obtain user information\"})\n        return\n    }\n    defer resp. Body.Close()\n    \n    body, _ := io. ReadAll(resp. Body)\n    var githubUser GithubUserResponse\n    if err := json. Unmarshal(body, &githubUser); err != nil {\n        c.JSON(400, gin. H{\"error\": \"User information parsing failed\"})\n        return\n    }\n    \n    Get user IP and geolocation\n    userIP := c.ClientIP()\n    Geolocation resolution logic...\n    \n    Find or create a user\n    var user UserModel\n    err = db. Where(\"github_id = ?\", strconv. Itoa(githubUser.ID)). First(&user). Error\n    if err != nil {\n        Create a new user\n        user = UserModel{\n            Username:   githubUser.Login,\n            Nickname:   githubUser.Name,\n            Avatar:     githubUser.AvatarURL,\n            GithubID:   strconv. Itoa(githubUser.ID),\n            Email:      githubUser.Email,\n            IP:         userIP,\n            Role: 2, \u002F\u002F Regular user\n            SignStatus: 1, \u002F\u002F GitHub Login\n        }\n        \n        if err := db. Create(&user). Error; err != nil {\n            c.JSON(500, gin. H{\"error\": \"User creation failed\"})\n            return\n        }\n    }\n    \n    Generate a JWT token\n    accessToken := generateAccessToken(user)\n    refreshToken := generateRefreshToken(user)\n    \n    Redirects to the front-end callback page\n    callbackURL := fmt. Sprintf(\"http:\u002F\u002Flocalhost:3000\u002Flogin\u002Fgithub\u002Fcallback?access_token=%s&refresh_token=%s\", \n        accessToken, refreshToken)\n    c.Redirect(http. StatusFound, callbackURL)\n}\n```\n\n### 3. Microsoft OAuth implementation\n\n#### OAuth configuration\n\n```go\ntype MicrosoftOAuth struct{}\n\nfunc (m MicrosoftOAuth) Config() *oauth2. Config {\n    return &oauth2. Config{\n        ClientID:     config. Microsoft.ClientID,\n        ClientSecret: config. Microsoft.ClientSecret,\n        RedirectURL:  config. Microsoft.RedirectURL,\n        Scopes:       []string{\"https:\u002F\u002Fgraph.microsoft.com\u002FUser.Read\"},\n        Endpoint: oauth2. Endpoint{\n            AuthURL:  \"https:\u002F\u002Flogin.microsoftonline.com\u002Fcommon\u002Foauth2\u002Fv2.0\u002Fauthorize\",\n            TokenURL: \"https:\u002F\u002Flogin.microsoftonline.com\u002Fcommon\u002Foauth2\u002Fv2.0\u002Ftoken\",\n        },\n    }\n}\n```\n\n#### User Information Structure\n\n```go\ntype MicrosoftUserResponse struct {\n    ID                string `json:\"id\"`\n    DisplayName       string `json:\"displayName\"`\n    GivenName         string `json:\"givenName\"`\n    UserPrincipalName string `json:\"userPrincipalName\"`\n    Mail              string `json:\"mail\"`\n}\n```\n\n#### Callback Handling (Key Differences)\n\n```go\nfunc MicrosoftOAuthCallbackView(c *gin. Context) {\n    Get the authorization code (note: use Query instead of Param)\n    code := c.Query(\"code\")\n    if code == \"\" {\n        c.JSON(400, gin. H{\"error\": \"authentication failed\"})\n        return\n    }\n    \n    Exchange tokens\n    config := MicrosoftOAuth{}. Config()\n    token, err := config. Exchange(c, code)\n    if err != nil {\n        c.JSON(400, gin. H{\"error\": \"authentication failed\"})\n        return\n    }\n    \n    Obtaining user information (using Microsoft Graph APIs)\n    client := config. Client(c, token)\n    resp, err := client. Get(\"https:\u002F\u002Fgraph.microsoft.com\u002Fv1.0\u002Fme\")\n    if err != nil {\n        c.JSON(400, gin. H{\"error\": \"Failed to obtain user information\"})\n        return\n    }\n    defer resp. Body.Close()\n    \n    body, _ := io. ReadAll(resp. Body)\n    var microsoftUser MicrosoftUserResponse\n    if err := json. Unmarshal(body, µsoftUser); err != nil {\n        c.JSON(400, gin. H{\"error\": \"User information parsing failed\"})\n        return\n    }\n    \n    Find or create a user\n    var user UserModel\n    err = db. Where(\"microsoft_id = ?\", microsoftUser.ID). First(&user). Error\n    if err != nil {\n        user = UserModel{\n            Username:    microsoftUser.UserPrincipalName,\n            Nickname:    microsoftUser.DisplayName,\n            MicrosoftID: microsoftUser.ID,\n            Email:       microsoftUser.Mail,\n            Role: 2, \u002F\u002F Regular user\n            SignStatus: 2, \u002F\u002F Microsoft Sign-in\n        }\n        \n        if err := db. Create(&user). Error; err != nil {\n            c.JSON(500, gin. H{\"error\": \"User creation failed\"})\n            return\n        }\n    }\n    \n    Generate JWT and redirect\n    accessToken := generateAccessToken(user)\n    refreshToken := generateRefreshToken(user)\n    \n    callbackURL := fmt. Sprintf(\"http:\u002F\u002Flocalhost:3000\u002Flogin\u002Fmicrosoft\u002Fcallback?access_token=%s&refresh_token=%s\", \n        accessToken, refreshToken)\n    c.Redirect(http. StatusFound, callbackURL)\n}\n```\n\n### 4. Routing configuration\n\n```go\nfunc InitOAuthRoutes(router *gin. RouterGroup) {\n    \u002F\u002F GitHub OAuth\n    router. GET(\"auth\u002Fgithub\", GithubOAuthRedirectView)\n    router. GET(\"auth\u002Fgithub\u002Fcallback\", GithubOAuthCallbackView)\n    \n    \u002F\u002F Microsoft OAuth\n    router. GET(\"auth\u002Fmicrosoft\", MicrosoftOAuthRedirectView)\n    router. GET(\"auth\u002Fmicrosoft\u002Fcallback\", MicrosoftOAuthCallbackView)\n}\n```\n\n## 3. Front-end implementation\n\n### 1. Login form component\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\"login-form\">\n        \u003Cdiv class=\"title\"> user login\u003C\u002Fdiv>\n\n                    \u003Ctemplate>\u003C\u002Ftemplate>\n\n                    \u003Ctemplate>\u003C\u002Ftemplate>\n\n            login\n\n        \u003Cdiv class=\"oauth-login\">\n            \u003Cdiv class=\"label\"> third-party login\u003C\u002Fdiv>\n            \u003Cdiv class=\"icons\">\n                \u003Ca>\n                    \u003Cimg src=\"\u002Fimages\u002Fgithub.png\" alt=\"GitHub login\" >\n                \u003C\u002Fa>\n                \u003Ca>\n                    \u003Cimg src=\"\u002Fimages\u002Fmicrosoft.svg\" alt=\"Microsoft login\" >\n                \u003C\u002Fa>\n            \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\n\n\u003Cstyle lang=\"scss\">\n.oauth-login {\n    margin-top: 20px;\n    \n    .label {\n        text-align: center;\n        color: #666;\n        margin-bottom: 15px;\n        position: relative;\n        \n        &::before, &::after {\n            content: \"\";\n            position: absolute;\n            top: 50%;\n            width: 30%;\n            height: 1px;\n            background-color: #ddd;\n        }\n        \n        &::before { left: 0; }\n        &::after { right: 0; }\n    }\n    \n    .icons {\n        display: flex;\n        justify-content: center;\n        gap: 20px;\n        \n        img {\n            width: 40px;\n            height: 40px;\n            cursor: pointer;\n            transition: transform 0.2s;\n            \n            &:hover {\n                transform: scale(1.1);\n            }\n        }\n    }\n}\n\u003C\u002Fstyle>\n```\n\n### 2. GitHub callback page\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\"oauth-callback\">\n        \u003Cdiv class=\"loading-container\">\n            \n            \u003Cp>GitHub login is being processed....\u003C\u002Fp>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\n\n\u003Cstyle lang=\"scss\">\n.oauth-callback {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    height: 100vh;\n    \n    .loading-container {\n        text-align: center;\n        \n        p {\n            margin-top: 16px;\n            color: #666;\n        }\n    }\n}\n\u003C\u002Fstyle>\n```\n\n### 3. Microsoft callback page\n\nThe Microsoft callback page is similar to GitHub, only you need to modify the relevant copy and identifiers:\n\n```vue\n\u003Ctemplate>\n    \u003Cdiv class=\"oauth-callback\">\n        \u003Cdiv class=\"loading-container\">\n            \n            \u003Cp>Microsoft sign-in is being processed....\u003C\u002Fp>\n        \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n\n\n```\n\n### 4. Routing configuration\n\n```typescript\nimport { createRouter, createWebHistory } from 'vue-router'\n\nconst router = createRouter({\n    history: createWebHistory(),\n    routes: [\n        {\n            path: \"\u002Flogin\",\n            name: \"login\",\n            component: () => import('@\u002Fviews\u002Flogin\u002Findex.vue')\n        },\n        {\n            path: \"\u002Flogin\u002Fgithub\u002Fcallback\",\n            name: \"github_callback\",\n            component: () => import('@\u002Fviews\u002Flogin\u002Fgithub-callback.vue')\n        },\n        {\n            path: \"\u002Flogin\u002Fmicrosoft\u002Fcallback\",\n            name: \"microsoft_callback\",\n            component: () => import('@\u002Fviews\u002Flogin\u002Fmicrosoft-callback.vue')\n        },\n        Other routes...\n    ]\n})\n\nexport default router\n```\n\n### 5. User status management\n\n```typescript\nimport { defineStore } from 'pinia'\n\ninterface UserInfo {\n    access_token: string\n    refresh_token: string\n    user_id: number\n    nickname: string\n    avatar: string\n    role: number\n}\n\nexport const useUserStore = defineStore('user', {\n    state(): { userInfo: UserInfo } {\n        return {\n            userInfo: {\n                access_token: \"\",\n                refresh_token: \"\",\n                user_id: 0,\n                nickname: \"\",\n                avatar: \"\",\n                role: 0\n            }\n        }\n    },\n    \n    actions: {\n        setToken(accessToken: string, refreshToken: string) {\n            this.userInfo.access_token = accessToken\n            this.userInfo.refresh_token = refreshToken\n            \n            Parse JWT to obtain user information\n            const payload = this.parseJWT(accessToken)\n            if (payload) {\n                this.userInfo.user_id = payload.user_id\n                this.userInfo.nickname = payload.nickname\n                this.userInfo.avatar = payload.avatar\n                this.userInfo.role = payload.role\n            }\n            \n            Persistent storage\n            localStorage.setItem(\"userInfo\", JSON.stringify(this.userInfo))\n        },\n        \n        parseJWT(token: string) {\n            try {\n                const base64Url = token.split('.') [1]\n                const base64 = base64Url.replace(\u002F-\u002Fg, '+').replace(\u002F_\u002Fg, '\u002F')\n                const jsonPayload = decodeURIComponent(\n                    atob(base64).split('').map(c => \n                        '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)\n                    ).join('')\n                )\n                return JSON.parse(jsonPayload)\n            } catch (error) {\n                console.error('JWT parsing failed:', error)\n                return null\n            }\n        },\n        \n        logout() {\n            this.userInfo = {\n                access_token: \"\",\n                refresh_token: \"\",\n                user_id: 0,\n                nickname: \"\",\n                avatar: \"\",\n                role: 0\n            }\n            localStorage.removeItem(\"userInfo\")\n        }\n    },\n    \n    getters: {\n        isLoggedIn(): boolean {\n            return !! this.userInfo.access_token && this.userInfo.user_id > 0\n        }\n    }\n})\n```\n\n## 4. OAuth application configuration\n\n### 1. GitHub OAuth apps\n\n1. Visit GitHub Developer Settings (https:\u002F\u002Fgithub.com\u002Fsettings\u002Fdevelopers)\n2. Click on \"New OAuth App\"\n3. Fill in the application information:\n   - Application name: The name of your app\n   - **Homepage URL**: 'http:\u002F\u002Flocalhost:3000' (development environment)\n   - **Authorization callback URL**: `http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fauth\u002Fgithub\u002Fcallback`\n4. Get the Client ID and Client Secret\n\n### 2. Microsoft OAuth apps\n\n1. Access the Azure portal (https:\u002F\u002Fportal.azure.com)\n2. Go to Azure Active Directory → App Registration\n3. Click on \"New Registration\"\n4. Fill in the application information:\n   - Name: The name of your app\n   - Supported account types: Accounts and personal Microsoft accounts in any organizational directory\n5. Configure Authentication:\n   - **Platform**: Web\n   - **Redirect URI**: 'http:\u002F\u002Flocalhost:8000\u002Fapi\u002Fauth\u002Fmicrosoft\u002Fcallback'\n   - **Token Configuration**: Tick \"ID Token\" and \"Access Token\"\n6. Generate a client key\n\n## 5. Common problems and solutions\n\n### 1. redirect_uri Mismatch\n\n**Error message**: 'The provided value for the input parameter 'redirect_uri' is not valid'\n\n**Solution**: \n- Ensure that the callback URLs in your OAuth app configuration are exactly the same as your backend code\n- Check whether the protocol (http\u002Fhttps), domain name, port, and path match\n- Notice the difference between 'localhost' and '127.0.0.1'\n\n### 2. scope permission configuration\n\n**GitHub**: \n- Basic information: '[]string{\"user:email\"}'\n- More permissions: '[]string{\"user\", \"user:email\"}'\n\n**Microsoft**: \n- Graph API：`[]string{\"https:\u002F\u002Fgraph.microsoft.com\u002FUser.Read\"}`\n- OpenID Connect：`[]string{\"openid\", \"profile\", \"email\"}`\n\n### 3. JSON parsing failed\n\nMake sure the struct's JSON tags match the API return field names:\n\n```go\nGitHub API return field\ntype GithubUser struct {\n    ID int 'json:\"id\"' \u002F\u002F correct\n    AvatarURL string 'json:\"avatar_url\"' \u002F\u002F Note the underscore\n}\n\nMicrosoft Graph API returns fields  \ntype MicrosoftUser struct {\n    ID string 'json:\"id\"' \u002F\u002F correct\n    DisplayName string 'json:\"displayName\"' \u002F\u002F Note the hump naming\n}\n```\n\n### 4. How to obtain an authorization code\n\n**The Right Way**:\n```go\ncode := c.Query(\"code\") \u002F\u002F Use Query to get the URL parameter\n```\n\n**Wrong Ways**:\n```go\ncode := c.Param(\"code\") \u002F\u002F Error: Param is used for path parameters\n```\n\n### 5. Frequent request limits\n\nAvoid frequent testing during the development phase:\n- Clear browser cache and cookies\n- Revoke app authorization with the OAuth provider\n- Use a different browser or incognito mode\n- Wait for a while and try again\n\n### 6. Front-end callback page issues\n\nMake sure the backend redirects to the frontend page instead of returning JSON:\n\n```go\nCorrect: Redirect to frontend\ncallbackURL := fmt. Sprintf(\"http:\u002F\u002Flocalhost:3000\u002Flogin\u002Fgithub\u002Fcallback?access_token=%s\", token)\nc.Redirect(http. StatusFound, callbackURL)\n\nError: Returns JSON\nc.JSON(200, gin. H{\"access_token\": token})\n```\n\n## 6. Safety considerations\n\n### 1. HTTPS usage\n\nProduction environments must use HTTPS:\n- OAuth providers require HTTPS callback URLs\n- Secure token transfers\n- Protection against man-in-the-middle attacks\n\n### 2. State parameter validation\n\n```go\nGenerate a random state\nstate := generateRandomString(32)\nSave to session or Redis\nsession. Set(\"oauth_state\", state)\n\nValidation on callback\ncallbackState := c.Query(\"state\")\nsessionState := session. Get(\"oauth_state\")\nif callbackState != sessionState {\n    c.JSON(400, gin. H{\"error\": \"Status validation failed\"})\n    return\n}\n```\n\n### 3. Token security\n\n- Set a reasonable JWT expiration time (e.g., 2 hours)\n- Implement the Refresh Token mechanism\n- Sensitive information is not stored in JWT payload\n- Sign JWTs with strong keys\n\n### 4. Enter verification\n\n```go\nValidate the required fields\nif user.ID == \"\" || user. Email == \"\" {\n    c.JSON(400, gin. H{\"error\": \"User information is incomplete\"})\n    return\n}\n\nMailbox format validation\nif !isValidEmail(user. Email) {\n    c.JSON(400, gin. H{\"error\": \"Invalid mailbox format\"})\n    return\n}\n```\n\n### 5. Error handling\n\nDon't expose sensitive information in misinformation:\n\n```go\nGood practice\nc.JSON(400, gin. H{\"error\": \"authentication failed\"})\n\nAvoid exposing detailed errors\nc.JSON(400, gin. H{\"error\": fmt. Sprintf(\"database error: %v\", err)})\n```\n\n## 7. Deployment precautions\n\n### 1. Environment variable configuration\n\n```bash\n# GitHub OAuth\nGITHUB_CLIENT_ID=your_github_client_id\nGITHUB_CLIENT_SECRET=your_github_client_secret\nGITHUB_REDIRECT_URL=https:\u002F\u002Fyourdomain.com\u002Fapi\u002Fauth\u002Fgithub\u002Fcallback\n\n# Microsoft OAuth  \nMICROSOFT_CLIENT_ID=your_microsoft_client_id\nMICROSOFT_CLIENT_SECRET=your_microsoft_client_secret\nMICROSOFT_REDIRECT_URL=https:\u002F\u002Fyourdomain.com\u002Fapi\u002Fauth\u002Fmicrosoft\u002Fcallback\n\n# JWT key\nJWT_SECRET=your_jwt_secret_key\n```\n\n### 2. Domain name configuration\n\nProduction environments need to be updated:\n- The callback URL of the OAuth app\n- The API address in the front-end environment variable\n- The frontend address of the backend redirect\n\n### 3. CORS configuration\n\n```go\nfunc CORSMiddleware() gin. HandlerFunc {\n    return gin. HandlerFunc(func(c *gin. Context) {\n        c.Header(\"Access-Control-Allow-Origin\", \"https:\u002F\u002Fyourdomain.com\")\n        c.Header(\"Access-Control-Allow-Methods\", \"GET, POST, OPTIONS\")\n        c.Header(\"Access-Control-Allow-Headers\", \"Content-Type, Authorization\")\n        \n        if c.Request.Method == \"OPTIONS\" {\n            c.AbortWithStatus(204)\n            return\n        }\n        \n        c.Next()\n    })\n}\n```\n\n## 8. Summary\n\nThis article details the complete scenario for Vue3 + Go to implement third-party OAuth sign-in, including:\n\n**Key Takeaways**:\n1. **Understanding the OAuth Process**: Master the complete process of authorization code patterns\n2. **Backend Implementation**: Handle redirects, callbacks, user information acquisition, and JWT generation correctly\n3. **Front-end implementation**: Clean login interface and callback page handling\n4. **Configuration Management**: Proper configuration and environment variable management for OAuth applications\n5. **Security Considerations**: State verification, HTTPS, token security, etc\n6. **Error Handling**: Well-developed error handling and user-friendly tips\n\n**Technical Gains**:\n- Practical application of OAuth 2.0 standard processes\n- Authentication scheme under the front-end and back-end separation architecture\n- Generation and validation of JWT tokens\n- Practice of the Vue3 Composition API\n- HTTP handling for the Go Gin framework\n\nThis complete implementation allows users to easily sign in to the app with their GitHub or Microsoft account, greatly improving the user experience. In actual projects, support for more OAuth providers can also be expanded as needed, such as Google, WeChat, QQ, etc.\n\n## Reference Resources\n\n- [OAuth 2.0 RFC Specification](https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc6749)\n- [GitHub OAuth documentation](https:\u002F\u002Fdocs.github.com\u002Fen\u002Fdevelopers\u002Fapps\u002Fbuilding-oauth-apps)\n- [Microsoft Graph API Documentation](https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fgraph\u002F)\n- [Vue.js Official Documentation](https:\u002F\u002Fvuejs.org\u002F)\n- [Gin Web Framework Documentation](https:\u002F\u002Fgin-gonic.com\u002F)","Go",0,"https:\u002F\u002Fblog4-1316398321.cos.ap-nanjing.myqcloud.com\u002Fblog5\u002F20250716162536__壮丽雪山-天空.png",[15,16,17],"GO","Gin","Vue",false,{"id":20,"title":21,"title_en":22},14,"Go 语言基础入门","Go language basics introduction",{"id":24,"title":25,"title_en":26},16,"Go 实现高性能敏感词过滤系统","Go implements high-performance sensitive word filtering system","2025-07-25T03:04:38+08:00"]