Vue3 + Go Complete guide to implementing third-party OAuth sign-in (GitHub & Microsoft).
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.
Project Architecture
- Frontend: Vue3 + TypeScript + Arco Design
- Backend: Go + Gin + GORM
- Database: MySQL
- OAuth providers: GitHub, Microsoft
1. Principle of OAuth login process
Standard OAuth 2.0 flow
User > Frontend > Backend Redirect Interface > OAuth Authorization Page > User Authorization >
OAuth callbacks > backends obtain user information > backends generate JWT >
Backend redirects to frontend > frontend saves token > jumps to the homepage
Detailed Steps:
- The user clicks on the third-party login button
- Jump to the backend redirect interface
- The backend redirects to the OAuth provider authorization page
- User authorization on the OAuth page
- The OAuth provider calls back the backend and returns the authorization code
- The backend exchanges the authorization code for access_token
- The backend uses access_token to obtain user information
- Create/update users on the backend and generate JWT tokens
- Backend redirects to frontend callback page (with token)
- Save the token on the frontend and jump to the target page
2. Back-end implementation
1. Database model design
go
type UserModel struct {
ID uint `gorm:"primaryKey"`
Username string
Nickname string
Email string
Avatar string
Password string
Third-party login ID (uniformly using string type is compatible with all platforms)
GithubID string `gorm:"index"`
MicrosoftID string `gorm:"index"`
Role int // User role
SignStatus int // Login method identifier
IP string
Addr string
CreatedAt time. Time
UpdatedAt time. Time
}
2. GitHub OAuth implementation
OAuth configuration
go
type GitHubOAuth struct{}
func (g GitHubOAuth) Config() *oauth2. Config {
return &oauth2. Config{
ClientID: config. GitHub.ClientID,
ClientSecret: config. GitHub.ClientSecret,
RedirectURL: config. GitHub.RedirectURL,
Scopes: []string{"user:email"},
Endpoint: github. Endpoint,
}
}
User Information Structure
go
type GithubUserResponse struct {
ID int `json:"id"`
Login string `json:"login"`
Name string `json:"name"`
Email string `json:"email"`
AvatarURL string `json:"avatar_url"`
}
Redirect interface
go
func GithubOAuthRedirectView(c *gin. Context) {
config := GitHubOAuth{}. Config()
url := config. AuthCodeURL("state", oauth2. AccessTypeOnline)
c.Redirect(http. StatusFound, url)
}
Callback handling
go
func GithubOAuthCallbackView(c *gin. Context) {
Get an authorization code
code := c.Query("code")
if code == "" {
c.JSON(400, gin. H{"error": "authentication failed"})
return
}
Exchange access_token
config := GitHubOAuth{}. Config()
token, err := config. Exchange(c, code)
if err != nil {
c.JSON(400, gin. H{"error": "authentication failed"})
return
}
Get user information
client := config. Client(c, token)
resp, err := client. Get("https://api.github.com/user")
if err != nil {
c.JSON(400, gin. H{"error": "Failed to obtain user information"})
return
}
defer resp. Body.Close()
body, _ := io. ReadAll(resp. Body)
var githubUser GithubUserResponse
if err := json. Unmarshal(body, &githubUser); err != nil {
c.JSON(400, gin. H{"error": "User information parsing failed"})
return
}
Get user IP and geolocation
userIP := c.ClientIP()
Geolocation resolution logic...
Find or create a user
var user UserModel
err = db. Where("github_id = ?", strconv. Itoa(githubUser.ID)). First(&user). Error
if err != nil {
Create a new user
user = UserModel{
Username: githubUser.Login,
Nickname: githubUser.Name,
Avatar: githubUser.AvatarURL,
GithubID: strconv. Itoa(githubUser.ID),
Email: githubUser.Email,
IP: userIP,
Role: 2, // Regular user
SignStatus: 1, // GitHub Login
}
if err := db. Create(&user). Error; err != nil {
c.JSON(500, gin. H{"error": "User creation failed"})
return
}
}
Generate a JWT token
accessToken := generateAccessToken(user)
refreshToken := generateRefreshToken(user)
Redirects to the front-end callback page
callbackURL := fmt. Sprintf("http://localhost:3000/login/github/callback?access_token=%s&refresh_token=%s",
accessToken, refreshToken)
c.Redirect(http. StatusFound, callbackURL)
}
3. Microsoft OAuth implementation
OAuth configuration
go
type MicrosoftOAuth struct{}
func (m MicrosoftOAuth) Config() *oauth2. Config {
return &oauth2. Config{
ClientID: config. Microsoft.ClientID,
ClientSecret: config. Microsoft.ClientSecret,
RedirectURL: config. Microsoft.RedirectURL,
Scopes: []string{"https://graph.microsoft.com/User.Read"},
Endpoint: oauth2. Endpoint{
AuthURL: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
TokenURL: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
},
}
}
User Information Structure
go
type MicrosoftUserResponse struct {
ID string `json:"id"`
DisplayName string `json:"displayName"`
GivenName string `json:"givenName"`
UserPrincipalName string `json:"userPrincipalName"`
Mail string `json:"mail"`
}
Callback Handling (Key Differences)
go
func MicrosoftOAuthCallbackView(c *gin. Context) {
Get the authorization code (note: use Query instead of Param)
code := c.Query("code")
if code == "" {
c.JSON(400, gin. H{"error": "authentication failed"})
return
}
Exchange tokens
config := MicrosoftOAuth{}. Config()
token, err := config. Exchange(c, code)
if err != nil {
c.JSON(400, gin. H{"error": "authentication failed"})
return
}
Obtaining user information (using Microsoft Graph APIs)
client := config. Client(c, token)
resp, err := client. Get("https://graph.microsoft.com/v1.0/me")
if err != nil {
c.JSON(400, gin. H{"error": "Failed to obtain user information"})
return
}
defer resp. Body.Close()
body, _ := io. ReadAll(resp. Body)
var microsoftUser MicrosoftUserResponse
if err := json. Unmarshal(body, µsoftUser); err != nil {
c.JSON(400, gin. H{"error": "User information parsing failed"})
return
}
Find or create a user
var user UserModel
err = db. Where("microsoft_id = ?", microsoftUser.ID). First(&user). Error
if err != nil {
user = UserModel{
Username: microsoftUser.UserPrincipalName,
Nickname: microsoftUser.DisplayName,
MicrosoftID: microsoftUser.ID,
Email: microsoftUser.Mail,
Role: 2, // Regular user
SignStatus: 2, // Microsoft Sign-in
}
if err := db. Create(&user). Error; err != nil {
c.JSON(500, gin. H{"error": "User creation failed"})
return
}
}
Generate JWT and redirect
accessToken := generateAccessToken(user)
refreshToken := generateRefreshToken(user)
callbackURL := fmt. Sprintf("http://localhost:3000/login/microsoft/callback?access_token=%s&refresh_token=%s",
accessToken, refreshToken)
c.Redirect(http. StatusFound, callbackURL)
}
4. Routing configuration
go
func InitOAuthRoutes(router *gin. RouterGroup) {
// GitHub OAuth
router. GET("auth/github", GithubOAuthRedirectView)
router. GET("auth/github/callback", GithubOAuthCallbackView)
// Microsoft OAuth
router. GET("auth/microsoft", MicrosoftOAuthRedirectView)
router. GET("auth/microsoft/callback", MicrosoftOAuthCallbackView)
}
3. Front-end implementation
1. Login form component
vue
<template>
<div class="login-form">
<div class="title"> user login</div>
<template></template>
<template></template>
login
<div class="oauth-login">
<div class="label"> third-party login</div>
<div class="icons">
<a>
<img src="/images/github.png" alt="GitHub login" >
</a>
<a>
<img src="/images/microsoft.svg" alt="Microsoft login" >
</a>
</div>
</div>
</div>
</template>
<style lang="scss">
.oauth-login {
margin-top: 20px;
.label {
text-align: center;
color: #666;
margin-bottom: 15px;
position: relative;
&::before, &::after {
content: "";
position: absolute;
top: 50%;
width: 30%;
height: 1px;
background-color: #ddd;
}
&::before { left: 0; }
&::after { right: 0; }
}
.icons {
display: flex;
justify-content: center;
gap: 20px;
img {
width: 40px;
height: 40px;
cursor: pointer;
transition: transform 0.2s;
&:hover {
transform: scale(1.1);
}
}
}
}
</style>
2. GitHub callback page
vue
<template>
<div class="oauth-callback">
<div class="loading-container">
<p>GitHub login is being processed....</p>
</div>
</div>
</template>
<style lang="scss">
.oauth-callback {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
.loading-container {
text-align: center;
p {
margin-top: 16px;
color: #666;
}
}
}
</style>
3. Microsoft callback page
The Microsoft callback page is similar to GitHub, only you need to modify the relevant copy and identifiers:
vue
<template>
<div class="oauth-callback">
<div class="loading-container">
<p>Microsoft sign-in is being processed....</p>
</div>
</div>
</template>
4. Routing configuration
typescript
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: "/login",
name: "login",
component: () => import('@/views/login/index.vue')
},
{
path: "/login/github/callback",
name: "github_callback",
component: () => import('@/views/login/github-callback.vue')
},
{
path: "/login/microsoft/callback",
name: "microsoft_callback",
component: () => import('@/views/login/microsoft-callback.vue')
},
Other routes...
]
})
export default router
5. User status management
typescript
import { defineStore } from 'pinia'
interface UserInfo {
access_token: string
refresh_token: string
user_id: number
nickname: string
avatar: string
role: number
}
export const useUserStore = defineStore('user', {
state(): { userInfo: UserInfo } {
return {
userInfo: {
access_token: "",
refresh_token: "",
user_id: 0,
nickname: "",
avatar: "",
role: 0
}
}
},
actions: {
setToken(accessToken: string, refreshToken: string) {
this.userInfo.access_token = accessToken
this.userInfo.refresh_token = refreshToken
Parse JWT to obtain user information
const payload = this.parseJWT(accessToken)
if (payload) {
this.userInfo.user_id = payload.user_id
this.userInfo.nickname = payload.nickname
this.userInfo.avatar = payload.avatar
this.userInfo.role = payload.role
}
Persistent storage
localStorage.setItem("userInfo", JSON.stringify(this.userInfo))
},
parseJWT(token: string) {
try {
const base64Url = token.split('.') [1]
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/')
const jsonPayload = decodeURIComponent(
atob(base64).split('').map(c =>
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
).join('')
)
return JSON.parse(jsonPayload)
} catch (error) {
console.error('JWT parsing failed:', error)
return null
}
},
logout() {
this.userInfo = {
access_token: "",
refresh_token: "",
user_id: 0,
nickname: "",
avatar: "",
role: 0
}
localStorage.removeItem("userInfo")
}
},
getters: {
isLoggedIn(): boolean {
return !! this.userInfo.access_token && this.userInfo.user_id > 0
}
}
})
4. OAuth application configuration
1. GitHub OAuth apps
- Visit GitHub Developer Settings (https://github.com/settings/developers)
- Click on "New OAuth App"
- Fill in the application information:
- Application name: The name of your app
- Homepage URL: 'http://localhost:3000' (development environment)
- Authorization callback URL:
http://localhost:8000/api/auth/github/callback
- Get the Client ID and Client Secret
2. Microsoft OAuth apps
- Access the Azure portal (https://portal.azure.com)
- Go to Azure Active Directory → App Registration
- Click on "New Registration"
- Fill in the application information:
- Name: The name of your app
- Supported account types: Accounts and personal Microsoft accounts in any organizational directory
- Configure Authentication:
- Platform: Web
- Redirect URI: 'http://localhost:8000/api/auth/microsoft/callback'
- Token Configuration: Tick "ID Token" and "Access Token"
- Generate a client key
5. Common problems and solutions
1. redirect_uri Mismatch
Error message: 'The provided value for the input parameter 'redirect_uri' is not valid'
Solution:
- Ensure that the callback URLs in your OAuth app configuration are exactly the same as your backend code
- Check whether the protocol (http/https), domain name, port, and path match
- Notice the difference between 'localhost' and '127.0.0.1'
2. scope permission configuration
GitHub:
- Basic information: '[]string{"user:email"}'
- More permissions: '[]string{"user", "user:email"}'
Microsoft:
- Graph API:
[]string{"https://graph.microsoft.com/User.Read"} - OpenID Connect:
[]string{"openid", "profile", "email"}
3. JSON parsing failed
Make sure the struct's JSON tags match the API return field names:
go
GitHub API return field
type GithubUser struct {
ID int 'json:"id"' // correct
AvatarURL string 'json:"avatar_url"' // Note the underscore
}
Microsoft Graph API returns fields
type MicrosoftUser struct {
ID string 'json:"id"' // correct
DisplayName string 'json:"displayName"' // Note the hump naming
}
4. How to obtain an authorization code
The Right Way:
go
code := c.Query("code") // Use Query to get the URL parameter
Wrong Ways:
go
code := c.Param("code") // Error: Param is used for path parameters
5. Frequent request limits
Avoid frequent testing during the development phase:
- Clear browser cache and cookies
- Revoke app authorization with the OAuth provider
- Use a different browser or incognito mode
- Wait for a while and try again
6. Front-end callback page issues
Make sure the backend redirects to the frontend page instead of returning JSON:
go
Correct: Redirect to frontend
callbackURL := fmt. Sprintf("http://localhost:3000/login/github/callback?access_token=%s", token)
c.Redirect(http. StatusFound, callbackURL)
Error: Returns JSON
c.JSON(200, gin. H{"access_token": token})
6. Safety considerations
1. HTTPS usage
Production environments must use HTTPS:
- OAuth providers require HTTPS callback URLs
- Secure token transfers
- Protection against man-in-the-middle attacks
2. State parameter validation
go
Generate a random state
state := generateRandomString(32)
Save to session or Redis
session. Set("oauth_state", state)
Validation on callback
callbackState := c.Query("state")
sessionState := session. Get("oauth_state")
if callbackState != sessionState {
c.JSON(400, gin. H{"error": "Status validation failed"})
return
}
3. Token security
- Set a reasonable JWT expiration time (e.g., 2 hours)
- Implement the Refresh Token mechanism
- Sensitive information is not stored in JWT payload
- Sign JWTs with strong keys
4. Enter verification
go
Validate the required fields
if user.ID == "" || user. Email == "" {
c.JSON(400, gin. H{"error": "User information is incomplete"})
return
}
Mailbox format validation
if !isValidEmail(user. Email) {
c.JSON(400, gin. H{"error": "Invalid mailbox format"})
return
}
5. Error handling
Don't expose sensitive information in misinformation:
go
Good practice
c.JSON(400, gin. H{"error": "authentication failed"})
Avoid exposing detailed errors
c.JSON(400, gin. H{"error": fmt. Sprintf("database error: %v", err)})
7. Deployment precautions
1. Environment variable configuration
bash
# GitHub OAuth
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITHUB_REDIRECT_URL=https://yourdomain.com/api/auth/github/callback
# Microsoft OAuth
MICROSOFT_CLIENT_ID=your_microsoft_client_id
MICROSOFT_CLIENT_SECRET=your_microsoft_client_secret
MICROSOFT_REDIRECT_URL=https://yourdomain.com/api/auth/microsoft/callback
# JWT key
JWT_SECRET=your_jwt_secret_key
2. Domain name configuration
Production environments need to be updated:
- The callback URL of the OAuth app
- The API address in the front-end environment variable
- The frontend address of the backend redirect
3. CORS configuration
go
func CORSMiddleware() gin. HandlerFunc {
return gin. HandlerFunc(func(c *gin. Context) {
c.Header("Access-Control-Allow-Origin", "https://yourdomain.com")
c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
}
8. Summary
This article details the complete scenario for Vue3 + Go to implement third-party OAuth sign-in, including:
Key Takeaways:
- Understanding the OAuth Process: Master the complete process of authorization code patterns
- Backend Implementation: Handle redirects, callbacks, user information acquisition, and JWT generation correctly
- Front-end implementation: Clean login interface and callback page handling
- Configuration Management: Proper configuration and environment variable management for OAuth applications
- Security Considerations: State verification, HTTPS, token security, etc
- Error Handling: Well-developed error handling and user-friendly tips
Technical Gains:
- Practical application of OAuth 2.0 standard processes
- Authentication scheme under the front-end and back-end separation architecture
- Generation and validation of JWT tokens
- Practice of the Vue3 Composition API
- HTTP handling for the Go Gin framework
This 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.