Caesar Auth
Learn how to secure your application with Caesar Auth.
Overview
The Caesar Auth package provides authentication and authorization functionalities for your application. It includes support for social logins (Google, Facebook, Github). This guide will walk you through setting up Caesar Auth and implementing common authentication features like sign up, sign in, sign out, and social authentication.
Installation
First, download the Caesar Auth package:
go get -u github.com/caesar-rocks/auth
Setting Up Caesar Auth
Validate Environment Variables
Before setting up Caesar Auth, ensure that the necessary environment variables are validated in the ./config/env.go
file:
package config
import (
"github.com/caesar-rocks/core"
)
// EnvironmentVariables is a struct that holds all the environment variables that need to be validated.
type EnvironmentVariables struct {
APP_KEY string `validate:"required"`
// Validate variables for your OAuth providers
GITHUB_OAUTH_KEY string
GITHUB_OAUTH_SECRET string
GITHUB_OAUTH_CALLBACK_URL string
// Other environment variables...
}
func ProvideEnvironmentVariables() *EnvironmentVariables {
return core.ValidateEnvironmentVariables[EnvironmentVariables]()
}
Configuration
Create a configuration file located at ./config/auth.go
:
package config
import (
"citadel/app/repositories"
"context"
"time"
"github.com/caesar-rocks/auth"
)
func ProvideAuth(env *EnvironmentVariables, usersRepository *repositories.UsersRepository) *auth.Auth {
return auth.NewAuth(&auth.AuthCfg{
Key: env.APP_KEY,
JWTSigningKey: []byte(env.APP_KEY),
MaxAge: time.Hour * 24 * 30,
JWTExpiration: time.Hour * 24 * 30,
UserProvider: func(ctx context.Context, userID any) (any, error) {
return usersRepository.FindOneBy(ctx, "id", userID)
},
RedirectTo: "/auth/sign_in", // Redirect path for unauthenticated users
SocialProviders: &map[string]auth.SocialAuthProvider{
"github": { // GitHub social provider (optional)
Key: env.GITHUB_OAUTH_KEY,
Secret: env.GITHUB_OAUTH_SECRET,
CallbackURL: env.GITHUB_OAUTH_CALLBACK_URL,
Scopes: []string{"user:email", "read:user"},
},
},
})
}
Register as a Provider
Register the ProvideAuth
function as a provider in the ./config/app.go
file:
package config
import (
"myapp/app/repositories"
"github.com/caesar-rocks/core"
)
func ProvideApp() *core.App {
app := core.NewApp(&core.AppConfig{
Addr: ":8080",
})
env := ProvideEnvironmentVariables()
auth := ProvideAuth(env, usersRepo)
// Register providers
app.RegisterProviders(
ProvideEnvironmentVariables,
ProvideAuth,
// Other providers...
)
// Other invokers...
return app
}
Using Caesar Auth in Controllers
Sign In Controller
Define a controller to handle the sign in functionality:
package authControllers
import (
"citadel/app/repositories"
authPages "citadel/views/pages/auth"
caesarAuth "github.com/caesar-rocks/auth"
caesar "github.com/caesar-rocks/core"
)
type SignInController struct {
auth *caesarAuth.Auth
usersRepository *repositories.UsersRepository
}
func (c *SignInController) Show(ctx *caesar.CaesarCtx) error {
return ctx.Render(authPages.SignInPage())
}
type SignInValidator struct {
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required,min=8"`
}
func (c *SignInController) Handle(ctx *caesar.CaesarCtx) error {
data, errors, ok := caesar.Validate[SignInValidator](ctx)
if !ok {
return ctx.Render(authPages.SignInForm(
authPages.SignInInput{Email: data.Email, Password: data.Password},
errors,
))
}
user, _ := c.usersRepository.FindOneBy(ctx.Context(), "email", data.Email)
if user == nil || !caesarAuth.CheckPasswordHash(data.Password, user.Password) {
return ctx.Render(authPages.SignInForm(
authPages.SignInInput{Email: data.Email, Password: data.Password},
map[string]string{"Auth": "Invalid credentials."},
))
}
return ctx.Redirect("/applications")
}
Sign Up Controller
Define a controller to handle the sign up functionality:
package authControllers
import (
"citadel/app/models"
"citadel/app/repositories"
"citadel/app/services"
authPages "citadel/views/pages/auth"
caesarAuth "github.com/caesar-rocks/auth"
caesar "github.com/caesar-rocks/core"
"github.com/caesar-rocks/events"
)
type SignUpController struct {
auth *caesarAuth.Auth
repo *repositories.UsersRepository
service *services.UsersService
emitter *events.EventsEmitter
}
type SignUpValidator struct {
Email string `form:"email" validate:"required,email"`
FullName string `form:"full_name" validate:"required,min=3"`
Password string `form:"password" validate:"required,min=8"`
}
func (c *SignUpController) Handle(ctx *caesar.CaesarCtx) error {
data, errors, ok := caesar.Validate[SignUpValidator](ctx)
if !ok {
return ctx.Render(authPages.SignUpForm(
authPages.SignUpInput{Email: data.Email, FullName: data.FullName, Password: data.Password},
errors,
))
}
user, _ := c.repo.FindOneBy(ctx.Context(), "email", data.Email)
if user != nil {
return ctx.Render(authPages.SignUpForm(
authPages.SignUpInput{Email: data.Email, FullName: data.FullName, Password: data.Password},
map[string]string{"Email": "Email is already in use."},
))
}
hashedPassword, err := caesarAuth.HashPassword(data.Password)
if err != nil {
return caesar.NewError(400)
}
user = &models.User{Email: data.Email, FullName: data.FullName, Password: hashedPassword}
if err := c.service.CreateAndEmitEvent(ctx.Context(), user); err != nil {
return caesar.NewError(400)
}
if err := c.auth.Authenticate(ctx, *user); err != nil {
return caesar.NewError(400)
}
return ctx.Redirect("/applications")
}
Sign Out Controller
Define a controller to handle the sign out functionality:
package authControllers
import (
"github.com/caesar-rocks/auth"
caesar "github.com/caesar-rocks/core"
)
type SignOutController struct {
auth *auth.Auth
}
func (c *SignOutController) Handle(ctx *caesar.CaesarCtx) error {
if err := c.auth.SignOut(ctx); err != nil {
return err
}
return ctx.Redirect("/auth/sign_in")
}
Github Social Auth Controller
Define a controller to handle Github social authentication:
package authControllers
import (
"citadel/app/models"
"citadel/app/repositories"
"citadel/app/services"
"github.com/caesar-rocks/auth"
caesar "github.com/caesar-rocks/core"
)
type GithubController struct {
auth *auth.Auth
repo *repositories.UsersRepository
service *services.UsersService
}
func (c *GithubController) Redirect(ctx *caesar.CaesarCtx) error {
return c.auth.Social.Use("github").Redirect(ctx)
}
func (c *GithubController) Callback(ctx *caesar.CaesarCtx) error {
oauthUser, err := c.auth.Social.Use("github").Callback(ctx)
if err != nil {
return caesar.NewError(400)
}
user, err := c.repo.FindOneBy(ctx.Context(), "github_user_id", oauthUser.UserID)
if err != nil {
user = &models.User{Email: oauthUser.Email, FullName: oauthUser.Name, GitHubUserID: oauthUser.UserID}
if err := c.service.CreateAndEmitEvent(ctx.Context(), user); err != nil {
return caesar.NewError(400)
}
}
if err := c.auth.Authenticate(ctx, *user); err != nil {
return caesar.NewError(400)
}
return ctx.Redirect("/applications")
}
Routes Configuration
Register your authentication and social auth routes:
package config
import (
"myapp/app/controllers"
"github.com/caesar-rocks/auth"
caesar "github.com/caesar-rocks/core"
)
func RegisterRoutes(
auth *auth.Auth,
usersRepo *repositories.UsersRepository,
) *caesar.Router {
signInController := authControllers.NewSignInController(auth, usersRepo)
signUpController := authControllers.NewSignUpController(auth, usersService, usersRepo, emitter)
signOutController := authControllers.NewSignOutController(auth)
githubController := authControllers.NewGithubController(auth, usersRepo, usersService)
router := caesar.NewRouter()
router.Use(auth.SilentMiddleware) // Optionally protects routes without redirecting unauthenticated users
// Auth routes
router.Get("/auth/sign_up", signUpController.Show)
router.Post("/auth/sign_up", signUpController.Handle)
router.Get("/auth/sign_in", signInController.Show)
router.Post("/auth/sign_in", signInController.Handle)
router.Post("/auth/sign_out", signOutController.Handle).Use(auth.AuthMiddleware) // Protects the route by requiring authentication
// OAuth-related routes
router.Get("/auth/github/redirect", githubController.Redirect)
router.Get("/auth/github/callback", githubController.Callback)
// Protected routes example
router.Patch("/apps/{slug}", appsController.Update).Use(auth.AuthMiddleware) // Protects the route by requiring authentication
return router
}
The SilentMiddleware
checks if a user is authenticated and stores this information without redirecting unauthenticated users.
The AuthMiddleware
ensures that the user is authenticated before accessing the protected route, redirecting them to the sign-in page if necessary.
Retrieving the User from Context
You can retrieve the authenticated user from the context in your controller using the following method:
package controllers
import (
"citadel/app/models"
"github.com/caesar-rocks/auth"
caesar "github.com/caesar-rocks/core"
)
func (c *SomeController) SomeProtectedAction(ctx *caesar.CaesarCtx) error {
user, err := auth.RetrieveUserFromCtx[models.User](ctx)
if err != nil {
return caesar.NewError(401, "Unauthorized")
}
// Use the user object for further processing
// ...
return ctx.Render(somePage)
}
Summary
The Caesar Auth package provides robust authentication and authorization features for your application. By following this guide, you can set up and use Caesar Auth to handle sign-up, sign-in, sign-out, and social authentication functionalities efficiently.