Gin OAuth Integration
Introduction
OAuth (Open Authorization) is an open standard for token-based authentication and authorization that allows third-party services to access resources on behalf of users without exposing their credentials. Integrating OAuth into your Gin web applications enables users to sign in using their existing accounts from providers like Google, GitHub, Facebook, and others.
This tutorial will guide you through the process of implementing OAuth authentication in a Gin web application. By the end, you'll understand how OAuth works and be able to set up secure authentication using popular OAuth providers.
Prerequisites
Before we start, make sure you have:
- Basic knowledge of Go programming
- Familiarity with Gin framework basics
- Go installed on your system (version 1.16+)
- A code editor
Understanding OAuth Flow
OAuth 2.0 authentication typically follows these steps:
- User clicks "Login with Provider" on your application
- Your application redirects to the provider's authorization page
- User authenticates with the provider and grants permissions
- Provider redirects back to your application with an authorization code
- Your application exchanges this code for an access token
- Your application uses the access token to fetch user information
Setting Up OAuth with Gin
Let's implement OAuth authentication using the popular goth
library, which provides a simple way to use OAuth with multiple providers.
Step 1: Install Required Packages
go get github.com/gin-gonic/gin
go get github.com/markbates/goth
go get github.com/markbates/goth/providers/google
go get github.com/markbates/goth/providers/github
Step 2: Create a Basic Gin Application Structure
Create a new file named main.go
with the following structure:
package main
import (
"html/template"
"log"
"net/http"
"os"
"github.com/gin-gonic/gin"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/github"
"github.com/markbates/goth/providers/google"
)
func main() {
r := gin.Default()
// We'll add our OAuth configuration here
r.GET("/", HomeHandler)
r.Run(":8080")
}
func HomeHandler(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "OAuth Authentication Example",
})
}
Step 3: Set Up OAuth Providers
First, we need to register OAuth providers. To do this, you'll need to create applications on the providers' developer portals to get client IDs and secrets.
For example:
- Google: Google Developer Console
- GitHub: GitHub Developer Settings
Here's how to configure the providers:
// Configure OAuth providers
func init() {
googleClientID := os.Getenv("GOOGLE_CLIENT_ID")
googleClientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
githubClientID := os.Getenv("GITHUB_CLIENT_ID")
githubClientSecret := os.Getenv("GITHUB_CLIENT_SECRET")
callbackURL := "http://localhost:8080/auth/callback"
goth.UseProviders(
google.New(googleClientID, googleClientSecret, callbackURL+"/google", "email", "profile"),
github.New(githubClientID, githubClientSecret, callbackURL+"/github", "user:email"),
)
// Set the session store for gothic
gothic.Store = gothic.SessionStore{}
}
Step 4: Create OAuth Routes and Handlers
Now, let's add the necessary routes for authentication:
func main() {
r := gin.Default()
// Load HTML templates
r.LoadHTMLGlob("templates/*")
// Home page
r.GET("/", HomeHandler)
// Auth routes
auth := r.Group("/auth")
{
auth.GET("/:provider", BeginAuthHandler)
auth.GET("/callback/:provider", CallbackHandler)
auth.GET("/logout", LogoutHandler)
}
// Protected route example
r.GET("/profile", AuthRequired, ProfileHandler)
r.Run(":8080")
}
Now, let's implement the authentication handlers:
// BeginAuthHandler initiates the authentication process
func BeginAuthHandler(c *gin.Context) {
provider := c.Param("provider")
// Set the provider name in the session
c.Request.URL.RawQuery = "provider=" + provider
// Begin the authentication process
gothic.BeginAuthHandler(c.Writer, c.Request)
}
// CallbackHandler handles the callback from the OAuth provider
func CallbackHandler(c *gin.Context) {
provider := c.Param("provider")
// Set the provider name in the session
c.Request.URL.RawQuery = "provider=" + provider
// Complete the authentication process
user, err := gothic.CompleteUserAuth(c.Writer, c.Request)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
// Store user information in session (in a real app)
session := sessions.Default(c)
session.Set("user_id", user.UserID)
session.Set("name", user.Name)
session.Set("email", user.Email)
session.Save()
c.Redirect(http.StatusTemporaryRedirect, "/profile")
}
// LogoutHandler logs out the user
func LogoutHandler(c *gin.Context) {
gothic.Logout(c.Writer, c.Request)
// Clear session in your session store
session := sessions.Default(c)
session.Clear()
session.Save()
c.Redirect(http.StatusTemporaryRedirect, "/")
}
// AuthRequired is a middleware to check if user is authenticated
func AuthRequired(c *gin.Context) {
session := sessions.Default(c)
userID := session.Get("user_id")
if userID == nil {
c.Redirect(http.StatusTemporaryRedirect, "/")
c.Abort()
return
}
c.Next()
}
// ProfileHandler shows the user's profile
func ProfileHandler(c *gin.Context) {
session := sessions.Default(c)
c.HTML(http.StatusOK, "profile.html", gin.H{
"name": session.Get("name"),
"email": session.Get("email"),
})
}
Step 5: Create HTML Templates
Create a templates directory and add index.html
:
<!DOCTYPE html>
<html>
<head>
<title>{{ .title }}</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.login-buttons {
margin: 30px 0;
}
.btn {
display: inline-block;
padding: 10px 20px;
margin-right: 10px;
background-color: #4285F4;
color: white;
text-decoration: none;
border-radius: 4px;
}
.github {
background-color: #24292e;
}
</style>
</head>
<body>
<h1>OAuth Authentication Example</h1>
<p>Click one of the buttons below to log in:</p>
<div class="login-buttons">
<a href="/auth/google" class="btn">Login with Google</a>
<a href="/auth/github" class="btn github">Login with GitHub</a>
</div>
</body>
</html>
Also create profile.html
:
<!DOCTYPE html>
<html>
<head>
<title>User Profile</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.profile {
border: 1px solid #ddd;
padding: 20px;
border-radius: 5px;
margin-top: 20px;
}
.logout {
display: inline-block;
padding: 10px 20px;
background-color: #f44336;
color: white;
text-decoration: none;
border-radius: 4px;
margin-top: 20px;
}
</style>
</head>
<body>
<h1>Welcome to Your Profile</h1>
<div class="profile">
<h2>{{ .name }}</h2>
<p><strong>Email:</strong> {{ .email }}</p>
</div>
<a href="/auth/logout" class="logout">Logout</a>
</body>
</html>
Step 6: Configure Sessions
To manage sessions, we need to add session middleware to our application. Let's use gin-contrib/sessions
:
go get github.com/gin-contrib/sessions
go get github.com/gin-contrib/sessions/cookie
Update your main.go
to include session middleware:
import (
// Other imports
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
)
func main() {
r := gin.Default()
// Set up session middleware
store := cookie.NewStore([]byte("secret-key"))
r.Use(sessions.Sessions("mysession", store))
// Rest of your code
}
Step 7: Full Implementation
Let's put everything together:
package main
import (
"log"
"net/http"
"os"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/github"
"github.com/markbates/goth/providers/google"
)
func init() {
// Set environment variables in your system or use a .env file in production
googleClientID := os.Getenv("GOOGLE_CLIENT_ID")
googleClientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
githubClientID := os.Getenv("GITHUB_CLIENT_ID")
githubClientSecret := os.Getenv("GITHUB_CLIENT_SECRET")
if googleClientID == "" || googleClientSecret == "" ||
githubClientID == "" || githubClientSecret == "" {
log.Fatal("Environment variables for OAuth clients not set")
}
callbackURL := "http://localhost:8080/auth/callback"
goth.UseProviders(
google.New(googleClientID, googleClientSecret, callbackURL+"/google", "email", "profile"),
github.New(githubClientID, githubClientSecret, callbackURL+"/github", "user:email"),
)
// Use gothic's session store
gothic.Store = gothic.SessionStore{}
}
func main() {
r := gin.Default()
// Set up session middleware
store := cookie.NewStore([]byte("secret-key"))
r.Use(sessions.Sessions("mysession", store))
// Load HTML templates
r.LoadHTMLGlob("templates/*")
// Routes
r.GET("/", HomeHandler)
auth := r.Group("/auth")
{
auth.GET("/:provider", BeginAuthHandler)
auth.GET("/callback/:provider", CallbackHandler)
auth.GET("/logout", LogoutHandler)
}
r.GET("/profile", AuthRequired, ProfileHandler)
r.Run(":8080")
}
func HomeHandler(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "OAuth Authentication Example",
})
}
func BeginAuthHandler(c *gin.Context) {
provider := c.Param("provider")
c.Request.URL.RawQuery = "provider=" + provider
gothic.BeginAuthHandler(c.Writer, c.Request)
}
func CallbackHandler(c *gin.Context) {
provider := c.Param("provider")
c.Request.URL.RawQuery = "provider=" + provider
user, err := gothic.CompleteUserAuth(c.Writer, c.Request)
if err != nil {
c.String(http.StatusInternalServerError, err.Error())
return
}
session := sessions.Default(c)
session.Set("user_id", user.UserID)
session.Set("name", user.Name)
session.Set("email", user.Email)
session.Set("avatar_url", user.AvatarURL)
session.Save()
c.Redirect(http.StatusTemporaryRedirect, "/profile")
}
func LogoutHandler(c *gin.Context) {
gothic.Logout(c.Writer, c.Request)
session := sessions.Default(c)
session.Clear()
session.Save()
c.Redirect(http.StatusTemporaryRedirect, "/")
}
func AuthRequired(c *gin.Context) {
session := sessions.Default(c)
userID := session.Get("user_id")
if userID == nil {
c.Redirect(http.StatusTemporaryRedirect, "/")
c.Abort()
return
}
c.Next()
}
func ProfileHandler(c *gin.Context) {
session := sessions.Default(c)
c.HTML(http.StatusOK, "profile.html", gin.H{
"name": session.Get("name"),
"email": session.Get("email"),
"avatar_url": session.Get("avatar_url"),
})
}
Step 8: Run Your Application
Before running your application, make sure to set the necessary environment variables:
export GOOGLE_CLIENT_ID=your_google_client_id
export GOOGLE_CLIENT_SECRET=your_google_client_secret
export GITHUB_CLIENT_ID=your_github_client_id
export GITHUB_CLIENT_SECRET=your_github_client_secret
Then run your application:
go run main.go
Visit http://localhost:8080
in your browser and you should see the login page with options to sign in using Google or GitHub.
Real-World Example: OAuth API Access
In a real-world scenario, you might want to use the OAuth token to access the provider's API. Here's an example of how to use the GitHub token to fetch the user's repositories:
// Add this import
import (
"encoding/json"
"fmt"
"io/ioutil"
)
// Add this route
r.GET("/repositories", AuthRequired, GetRepositoriesHandler)
// Add this handler
func GetRepositoriesHandler(c *gin.Context) {
session := sessions.Default(c)
provider := session.Get("provider")
accessToken := session.Get("access_token")
if provider != "github" || accessToken == nil {
c.String(http.StatusBadRequest, "GitHub authentication required")
return
}
// Call GitHub API to get repositories
req, _ := http.NewRequest("GET", "https://api.github.com/user/repos", nil)
req.Header.Set("Authorization", fmt.Sprintf("token %s", accessToken))
req.Header.Set("Accept", "application/vnd.github.v3+json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
c.String(http.StatusInternalServerError, "Error fetching repositories: "+err.Error())
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
var repos []map[string]interface{}
json.Unmarshal(body, &repos)
c.HTML(http.StatusOK, "repositories.html", gin.H{
"repositories": repos,
})
}
You'll also need to store the provider and access token in the session during the callback:
// In the CallbackHandler function, add:
session.Set("provider", provider)
session.Set("access_token", user.AccessToken)
Security Considerations
When implementing OAuth in production applications, keep these security best practices in mind:
-
Store secrets securely: Never hardcode client secrets. Use environment variables or a secrets manager.
-
Use HTTPS: Always use HTTPS in production to protect tokens in transit.
-
State parameter: Implement CSRF protection by using the state parameter in OAuth flows.
// Example of using state parameter
func BeginAuthHandler(c *gin.Context) {
provider := c.Param("provider")
// Generate random state
state := generateRandomString(32)
// Store state in session
session := sessions.Default(c)
session.Set("oauth_state", state)
session.Save()
c.Request.URL.RawQuery = fmt.Sprintf("provider=%s&state=%s", provider, state)
gothic.BeginAuthHandler(c.Writer, c.Request)
}
func CallbackHandler(c *gin.Context) {
provider := c.Param("provider")
// Verify state parameter
session := sessions.Default(c)
expectedState := session.Get("oauth_state")
actualState := c.Query("state")
if expectedState == nil || expectedState.(string) != actualState {
c.String(http.StatusBadRequest, "OAuth state mismatch - possible CSRF attack")
return
}
// Continue with OAuth flow
// ...
}
// Helper function to generate a random string
func generateRandomString(length int) string {
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
for i := range b {
b[i] = letterBytes[rand.Intn(len(letterBytes))]
}
return string(b)
}
-
Scope minimization: Only request the permissions (scopes) that your application actually needs.
-
Token storage: Store tokens securely and refresh when needed.
Summary
In this tutorial, you've learned how to:
- Set up OAuth integration in a Gin web application
- Configure multiple OAuth providers (Google and GitHub)
- Implement the OAuth authentication flow
- Create protected routes that require authentication
- Store user session data
- Access provider APIs using OAuth tokens
- Apply security best practices for OAuth implementation
OAuth authentication provides a secure and convenient way for users to access your application. It delegates the responsibility of credential management to trusted providers and gives users control over what information they share with your application.
Additional Resources
- OAuth 2.0 Specification
- Markbates Goth Documentation
- Gin Framework Documentation
- Google OAuth Documentation
- GitHub OAuth Documentation
Exercises
- Add another OAuth provider like Facebook or Twitter
- Implement a user profile page that shows more details from the OAuth provider
- Create a system to link multiple OAuth providers to a single user account
- Add token refresh functionality for providers that require it
- Implement a "remember me" feature that keeps users logged in across browser sessions
By completing these exercises, you'll gain a deeper understanding of OAuth authentication and be ready to implement advanced authentication features in your Gin web applications.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)