This project uses the Gin framework to implement authentication and supports both REST API and GraphQL.
This framework is designed to seamlessly support both RESTful APIs and GraphQL, allowing developers to choose the best approach for their application needs. Here’s how it achieves this compatibility:
-
Unified Controller Logic:
- The project structure includes a dedicated
controllersdirectory where the logic for handling requests is implemented. This allows for a clear separation of concerns, making it easy to manage both REST and GraphQL endpoints within the same application.
- The project structure includes a dedicated
-
Routing:
- The
routesdirectory defines the routing for RESTful endpoints, while the GraphQL endpoints are handled through a dedicated GraphQL server setup. This allows both types of requests to coexist without conflict.
- The
-
GraphQL Resolvers:
- The
graphdirectory contains resolvers that handle GraphQL queries and mutations. These resolvers can interact with the same underlying services and models used by the REST API, ensuring that business logic is reused effectively.
- The
-
Shared Models:
- The
modelsdirectory defines data structures that are used by both the REST API and GraphQL. This promotes consistency in data handling and reduces duplication of code.
- The
-
Middleware Support:
- Middleware functions defined in the
middlewaresdirectory can be applied to both REST and GraphQL routes. This allows for shared functionality such as authentication, logging, and error handling across both types of requests.
- Middleware functions defined in the
-
Flexible Response Handling:
- The framework includes utilities for formatting responses, which can be adapted for both JSON responses (for REST) and GraphQL responses. This ensures that clients receive data in the expected format regardless of the endpoint type. Refer to
utils\response\response.go
- The framework includes utilities for formatting responses, which can be adapted for both JSON responses (for REST) and GraphQL responses. This ensures that clients receive data in the expected format regardless of the endpoint type. Refer to
-
Configuration:
- The application can be configured to enable or disable either REST or GraphQL features as needed, providing flexibility for different use cases.
By leveraging these design principles, the Gin-Auth framework provides a robust solution for building applications that require both RESTful and GraphQL APIs, allowing developers to choose the best approach for their specific requirements.
-
GOLANG Environment
My Environment is
go version go1.23.2 windows/amd64Download: https://go.dev/dl/
-
PostgreSQL
-
Redis
-
minIO
-
Clone this repo
git clone xxx cd gin-auth -
Install the dependencies
go mod download
-
Setup environment variables
create a
.envfile in project root directory, you can refer to.env.exampleor# postgresql POSTGRES_USER=YOUR_POSTGRESQL_USERNAME POSTGRES_PASSWORD=YOUR_POSTGRESQL_PASSWORD POSTGRES_DB=YOUR_POSTGRESQL_DATABASE_NAME POSTGRES_HOST=YOUR_POSTGRESQL_HOST POSTGRES_PORT=YOUR_POSTGRESQL_PORT # default is 5432 # redis REDIS_HOST=YOUR_REDIS_HOST REDIS_PORT=YOUR_REDIS_PORT # default is 6379 REDIS_PASSWORD=YOUR_REDIS_PASSWORD REDIS_DB=YOUR_REDIS_DB # default is 0 # minio MINIO_URL=YOUR_MINIO_URL # URL CANNOT contain http:// or https:// MINIO_ACCESS_KEY_ID=YOUR_MINIO_ACCESS_KEY_ID MINIO_ACCESS_KEY_SECRET=YOUR_MINIO_ACCESS_KEY_SECRET MINIO_BUCKET_NAME=YOUR_MINIO_BUCKET_NAME # public MINIO_ROOT_USER=YOUR_MINIO_ROOT_USER MINIO_ROOT_PASSWORD=YOUR_MINIO_ROOT_PASSWORD # backend PREFIX=http:// HOST=YOUR_HOST PORT=YOUR_PORT # default is 8080 BACKEND_URL=${PREFIX}${HOST}:${PORT} # secret ARGON2_SALT=YOUR_ARGON2_SALT # smtp config SMTP_SERVER=YOUR_SMTP_SERVER # example: smtp.163.com SMTP_USER=YOUR_SMTP_USER SMTP_PASS=YOUR_SMTP_PASS SMTP_PORT=YOUR_SMTP_PORT # default is smtp.163.com is 465 SMTP_FROM_ADDRESS=YOUR_SMTP_FROM_ADDRESS # example: [email protected] SMTP_FROM_NAME=gin-auth # example: gin-auth # enable log LOG_ENABLE=false
-
Generate graphql related code
sh gql.sh-
Run Databases
# minio for windows or you can use docker $env:MINIO_ROOT_USER = "admin" $env:MINIO_ROOT_PASSWORD = "12345678" D:\App\MinIO\minio.exe server D:\App\MinIO\Data --console-address ":9001" # this is general command for mc.exe, run in cmd D:\App\MinIO\mc.exe alias set 'myminio' 'http://ip:port' 'USER' 'PASSWORD' # redis redis-server # postgresql ... # create database CREATE database gin-auth; # load the two tables refer to /resources/*.sql
-
Start the server
go run main.go
This project is organized into several directories and files, each serving a specific purpose. Below is an overview of the project structure:
├───.private # Directory for storing private keys
├───.public # Directory for storing public keys
├───main.go # Entry point of the application
├───.env # Environment variables configuration file
├───go.mod # Go module file for dependency management
├───go.sum # Go module checksum file
├───gql.sh # Script for GraphQL operations
├───gqlgen.yml # Configuration file for gqlgen
├───.gitignore # Specifies files and directories to ignore in Git
├───README.md # Project documentation
├───controllers # Contains controller logic for handling requests
│ ├───auth # Authentication-related controllers
│ ├───file # File handling controllers
│ └───user # User-related controllers
├───databases # Database migration and seed files
├───graph # GraphQL-related files
│ ├───custom # Custom GraphQL types and resolvers
│ ├───graphqls # GraphQL schema definitions
│ ├───model # GraphQL models
│ └───resolvers # GraphQL resolvers
├───logs # Directory for application logs
├───middlewares # Middleware functions for request processing
├───models # Data models and request structures
│ └───requests # Request models
├───repositories # Data access layer for interacting with the database
├───routes # Application routing definitions
├───services # Business logic layer
│ ├───auth # Authentication services
│ └───user # User services
└───utils # Utility functions and helpers
├───consts # Constants used throughout the application
├───cron # Cron job management
├───crypto # Cryptographic functions
├───file # File handling utilities
├───flow # Workflow management utilities
├───jwkmanager # JSON Web Key management
├───jwt # JWT (JSON Web Token) handling
├───mail # Email sending utilities
├───response # Response formatting utilities
└───validation # Input validation utilities
- .private / .public: These directories store the private and public keys used for signing and verifying JWTs.
- main.go: The main entry point of the application where the server is initialized and started.
- controllers: Contains the logic for handling incoming requests and returning responses.
- graph: Contains all GraphQL-related files, including schema definitions and resolvers.
- middlewares: Middleware functions that can be applied to routes for additional processing, such as authentication.
- models: Defines the data structures used in the application, including request models.
- repositories: Contains the data access layer for interacting with the database.
- services: Contains the business logic of the application, separating it from the controller layer.
- utils: A collection of utility functions and helpers that are used throughout the application.
This structure promotes a clean separation of concerns, making the codebase easier to navigate and maintain.
Use refresh token and access token.
-
Note that ACCESS TOKENS ARE NOT STORED IN DATABASE
-
refresh token default 90 days
-
access token default 60 minutes
-
you can modify in
utils\consts\const.go, theJWT_ACCESS_TOKEN_EXPIRYandJWT_REFRESH_TOKEN_EXPIRY -
Refresh tokens are store in
refresh_tokentable, you can refer toresources\refreshToken.sqlfor more details. -
Refresh token is random string
-
Access token claims, you can modify as you need:
# utils\jwt\jwt.go publicClaims := jwt.Claims{ Issuer: consts.JWT_ISSUER, Subject: user.ID, // Audience: IssuedAt: jwt.NewNumericDate(issuedAt), Expiry: jwt.NewNumericDate(issuedAt.Add(time.Duration(consts.JWT_ACCESS_TOKEN_EXPIRY) * time.Minute)), } // private claims privateClaims := map[string]interface{}{ "email": user.Email, // YOU CAN ADD MORE PRIVATE CLAIMS HERE }
-
Access tokens are generated based on JWK (JSON Web Key) and are signed to ensure their integrity and authenticity.
-
The access token contains public claims such as the issuer, subject (user ID), issued at time, and expiry time. The expiry time is set based on the
JWT_ACCESS_TOKEN_EXPIRYconstant, which you can adjust as needed. -
The private claims can include additional user-specific information, such as the user's email, and can be extended to include other relevant data.
-
Access tokens are typically sent in the
Authorizationheader of HTTP requests as a Bearer token:Authorization: Bearer <access_token> -
Ensure to handle token expiration properly in your application. When an access token expires, the client should use the refresh token to obtain a new access token.
-
Access tokens are stateless and do not require server-side storage, making them suitable for distributed systems.
-
To decrypt and validate the token, you can use the
ParseTokenandParseJWTClaimsfunctions inutils/jwt/jwt.go. These functions will extract the claims from the token and verify its signature using the appropriate public key. -
The keys used for signing the tokens are automatically updated on a scheduled basis. The key rotation is handled by a cron job that runs every Sunday, ensuring that your application uses fresh keys for signing tokens. This enhances security by limiting the lifespan of any given key.
-
For more details on how to implement token generation, validation, and key management, refer to the
utils/jwt/jwt.goandutils/jwkmanager/jwk.gofiles.
To ensure that incoming requests contain valid data, this project implements a robust validation mechanism for request parameters. The validation is handled using the go-playground/validator package, which allows for flexible and customizable validation rules.
-
Request Structs:
- Each request type is defined as a struct in the
models/requestspackage. These structs include validation tags that specify the required fields and validation rules. For example:
type UserRegisterRequest struct { Username string `json:"username" form:"username" validate:"required"` Email string `json:"email" form:"email" validate:"required,email"` Password string `json:"password" form:"password" validate:"required,min=6"` }
- Each request type is defined as a struct in the
-
Global Validator:
- A global validator instance is created in
models/requests/validator.go, which is used to validate the request structs. TheFormatErrorfunction formats validation errors and provides custom error messages based on the validation tags.
- A global validator instance is created in
-
Validation Methods:
- Each request struct implements a
Validatemethod that calls the global validator and formats any errors. This method can be called in both REST API and GraphQL resolvers to ensure that the incoming data is valid.
- Each request struct implements a
-
Binding and Validating:
- The
BindAndValidatefunction inutils/validation/validation.gois used to bind incoming request data to the request struct and validate it. This function works for both REST API and GraphQL requests, ensuring a consistent validation approach across the application.
func BindAndValidate(c *gin.Context, request interface{}) error { if err := c.ShouldBind(request); err != nil { response.BadRequestWithMessage(c, err.Error()) return err } if err := request.(interface{ Validate() error }).Validate(); err != nil { response.BadRequestWithMessage(c, err.Error()) return err } return nil }
- The
-
Custom Error Messages:
- Custom error messages for validation failures are defined in the
models/requests/auth.gofile. This allows for user-friendly error messages that can be returned in the response when validation fails.
- Custom error messages for validation failures are defined in the
In the REST API controller, the validation is performed as follows:
func Register(c *gin.Context) {
var request requests.UserRegisterRequest
if err := validation.BindAndValidate(c, &request); err != nil {
return
}
err := authService.Register(&request)
if err != nil {
response.BadRequestWithMessage(c, err.Error())
return
}
response.Success(c)
}In the GraphQL resolver, the validation is similarly invoked:
func (r *mutationResolver) Register(ctx context.Context, input requests.UserRegisterRequest) (bool, error) {
if err := input.Validate(); err != nil {
return false, err
}
err := authService.Register(&input)
if err != nil {
return false, err
}
return true, nil
}By implementing this validation mechanism, the application ensures that all incoming requests are properly validated, reducing the risk of errors and improving overall reliability.
go gorm
https://gorm.io/docs/query.html
Github Action + Docker Hub https://www.gclhaha.top/building/dockerhub.html#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6
argon2
https://www.alexedwards.net/blog/how-to-hash-and-verify-passwords-with-argon2-in-go
unix timestamp converter