Building a gRPC Login Service in Go
Development Environment
Windows 10
VS Code 1.69.2
Go 1.18.3
Project Overview
Implementation of a login service using gRPC with multiple client examples.
Data base Schema
CREATE TABLE `user_accounts` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`username` varchar(32) NOT NULL,
`password_hash` varchar(32) NOT NULL,
`company_id` int(10) NOT NULL,
`created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`last_login` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`is_deleted` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `user_accounts` VALUES
(1, 'testuser', 'e10adc3949ba59abbe56e057f20f883e', 1, '0000-00-00 00:00:00', '0000-00-00 00:00:00', 0);
Implementation Process
Project Initialization
git init
echo "# user-service" >> README.md
git add README.md
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/example/user-service.git
git push -u origin main
Protocol Buffer Definition
Create proto/user_auth/login.proto:
syntax = "proto3";
package auth;
option go_package = "./;auth";
message Credentials {
string username = 1;
string password = 2;
string signature = 3;
}
message AuthResponse {
string status_code = 1;
string message = 2;
string user_data = 3;
string auth_token = 4;
}
service Authentication {
rpc Authenticate(Credentials) returns (AuthResponse) {}
}
Generate gRPC code:
protoc --proto_path=./proto/user_auth/ *.proto --go-grpc_out=./proto/user_auth/
protoc --proto_path=./proto/user_auth/ *.proto --go_out=./proto/user_auth/
Server Implementation
package main
import (
"context"
"fmt"
"auth"
"net"
"google.golang.org/grpc"
)
type AuthServer struct{}
func (s *AuthServer) Authenticate(ctx context.Context, creds *auth.Credentials) (*auth.AuthResponse, error) {
fmt.Printf("Authentication attempt: %s\n", creds.Username)
return &auth.AuthResponse{
StatusCode: "200",
Message: "Authentication successful",
UserData: "user_profile",
AuthToken: "generated_token_123",
}, nil
}
func main() {
listener, err := net.Listen("tcp", ":8888")
if err != nil {
panic(err)
}
grpcServer := grpc.NewServer()
auth.RegisterAuthenticationServer(grpcServer, &AuthServer{})
fmt.Println("Server listening on :8888")
if err := grpcServer.Serve(listener); err != nil {
panic(err)
}
}
Go Client Implementation
package main
import (
"context"
"flag"
"fmt"
"auth"
"time"
"google.golang.org/grpc"
)
var (
serverAddr = flag.String("server", ":8888", "Server address")
user = flag.String("user", "testuser", "Username")
pass = flag.String("pass", "123456", "Password")
sig = flag.String("sig", "test_signature", "Request signature")
)
func authenticate(client auth.AuthenticationClient, creds *auth.Credentials) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
response, err := client.Authenticate(ctx, creds)
if err != nil {
fmt.Printf("Authentication error: %v\n", err)
return
}
fmt.Printf("Response: %v\n", response)
}
func main() {
flag.Parse()
conn, err := grpc.Dial(*serverAddr, grpc.WithInsecure())
if err != nil {
fmt.Printf("Connection failed: %v\n", err)
return
}
defer conn.Close()
client := auth.NewAuthenticationClient(conn)
authenticate(client, &auth.Credentials{
Username: *user,
Password: *pass,
Signature: *sig,
})
}
Configurasion Management
Create config/config.go:
package config
import (
"github.com/tietang/props/ini"
)
func GetServerPort() string {
config := ini.NewIniFileConfigSource("config.ini")
return config.GetDefault("server.port", "8888")
}
func GetDatabaseDSN() string {
config := ini.NewIniFileConfigSource("config.ini")
user := config.GetDefault("db.user", "root")
pass := config.GetDefault("db.password", "")
host := config.GetDefault("db.host", "localhost")
port := config.GetDefault("db.port", "3306")
name := config.GetDefault("db.name", "test")
return user + ":" + pass + "@tcp(" + host + ":" + port + ")/" + name +
"?charset=utf8mb4&parseTime=True&loc=Local"
}
Database Integration
package database
import (
"fmt"
"config"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type UserAccount struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"size:32"`
PasswordHash string `gorm:"size:32"`
CompanyID uint
CreatedAt string `gorm:"type:datetime"`
LastLogin string `gorm:"type:datetime"`
IsDeleted bool
}
func Connect() (*gorm.DB, error) {
dsn := config.GetDatabaseDSN()
return gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
func FindUser(username, passwordHash string) (*UserAccount, error) {
db, err := Connect()
if err != nil {
return nil, err
}
var account UserAccount
result := db.Where("username = ? AND password_hash = ?", username, passwordHash).First(&account)
if result.Error != nil {
return nil, result.Error
}
return &account, nil
}