Skip to content

Commit

Permalink
feat(authentication): Could finally protect routes
Browse files Browse the repository at this point in the history
Now routes are protected, added authentication functionality, made some changes in config.

BREAKING CHANGE: Can now protect routes with Auth0
Referred issue: #30
  • Loading branch information
FranCalveyra committed Jan 28, 2025
1 parent f353c0f commit b278063
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 30 deletions.
8 changes: 6 additions & 2 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
DATABASE_URL=<CONTAINER_URL># This is for the .env to get the Docker container URL
HOST=<DB_HOST># Can be localhost or the IP address of the database
HOST=<DB_HOST>
POSTGRES_DB=<DB_NAME>
POSTGRES_USER=<DB_USER>
POSTGRES_PASSWORD=<DB_PASSWORD>
Expand All @@ -19,4 +19,8 @@ AUTH0_CLIENT_SECRET=<AUTH0_CLIENT_SECRET>

# The Callback URL of our application. Customizable.
AUTH0_CALLBACK_URL=<AUTH0_CALLBACK_URL>
AUTH0_AUDIENCE=<CUSTOM_API_IDENTIFIER>
AUTH0_AUDIENCE=<CUSTOM_API_IDENTIFIER>

# These two are different from the previous ones, from these ones we're getting the Management API JWT
AUTH0_TEST_CLIENT_ID=<AUTH0_TEST_APP_CLIENT_ID>
AUTH0_TEST_CLIENT_SECRET=<AUTH0_TEST_APP_CLIENT_SECRET>
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ WORKDIR /root/

COPY --from=builder /app/rpl-service .

COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

CMD ["./rpl-service"]
7 changes: 4 additions & 3 deletions config/databaseConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import (
)

func StartDatabase() *gorm.DB {
// Retrieve environment variables
//err := godotenv.Load(".env")
//if err != nil {
// Uncomment these lines when running in local
// // Retrieve environment variables
// err := godotenv.Load(".env")
// if err != nil {
// log.Fatalf("Error loading .env file: %v", err)
// return nil
//}
Expand Down
28 changes: 22 additions & 6 deletions config/routerConfig.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package config

import (
"fmt"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"net/http"
"rpl-service/controllers/course"
"rpl-service/models"
"rpl-service/platform/middleware"
Expand All @@ -22,13 +24,27 @@ func mapToGinRoute(router *gin.Engine, endpoint models.Endpoint, db *gorm.DB) {
"DELETE": router.DELETE,
}

// Validate method exists
method, exists := methods[endpoint.Method]
if !exists {
panic(fmt.Sprintf("Unsupported method: %s", endpoint.Method))
}

// Create the base handler
handler := func(w http.ResponseWriter, r *http.Request) {
endpoint.HandlerFunction(w, r, db)
}

// Apply middleware if the route is protected
if endpoint.IsProtected {
methods[endpoint.Method](endpoint.Path, middleware.IsAuthenticated, func(ctx *gin.Context) {
endpoint.HandlerFunction(ctx.Writer, ctx.Request, db)
})
method(endpoint.Path, gin.WrapH(middleware.EnsureValidToken()(
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler(w, r)
}))))
} else {
methods[endpoint.Method](endpoint.Path, func(ctx *gin.Context) {
endpoint.HandlerFunction(ctx.Writer, ctx.Request, db)
})
// Unprotected route
method(endpoint.Path, gin.WrapF(func(w http.ResponseWriter, r *http.Request) {
handler(w, r)
}))
}
}
3 changes: 3 additions & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
package constants

import "time"

const EmptyString = ""
const ProviderDuration = 5 * time.Minute
14 changes: 7 additions & 7 deletions main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"rpl-service/config"
"rpl-service/constants"
"rpl-service/platform/authenticator"
"rpl-service/platform/router"
)

Expand Down Expand Up @@ -38,15 +39,14 @@ func startServer() {
}(s)

// Initialize authenticator
//auth, err := authenticator.New()
//if err != nil {
// fmt.Println("Failed to start authenticator")
// return
//}
auth, err := authenticator.New()
if err != nil {
log.Panicf("Failed to start authenticator: %v", err)
return
}

// Initialize the ginRouter
ginRouter := router.New()
config.InitializeRoutes(ginRouter, db)
ginRouter := router.New(auth, db)

serverPort := os.Getenv("SERVER_PORT")

Expand Down
16 changes: 5 additions & 11 deletions platform/authenticator/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package authenticator
import (
"context"
"errors"
"fmt"
"os"

"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
"os"
)

// Authenticator is used to authenticate our users.
Expand All @@ -17,23 +17,17 @@ type Authenticator struct {

// New instantiates the *Authenticator.
func New() (*Authenticator, error) {
domain := os.Getenv("AUTH0_DOMAIN")
issuer := "https://" + domain + "/"

fmt.Println(issuer)

provider, err := oidc.NewProvider(
context.Background(),
issuer,
"https://"+os.Getenv("AUTH0_DOMAIN")+"/",
)
if err != nil {
fmt.Println("Error creating the provider")
return nil, err
}

conf := oauth2.Config{
ClientID: os.Getenv("AUTH0_CLIENT_ID"),
ClientSecret: os.Getenv("AUTH0_CLIENT_SECRET"),
ClientID: os.Getenv("AUTH0_TEST_CLIENT_ID"),
ClientSecret: os.Getenv("AUTH0_TEST_CLIENT_SECRET"),
RedirectURL: os.Getenv("AUTH0_CALLBACK_URL"),
Endpoint: provider.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile"},
Expand Down
72 changes: 72 additions & 0 deletions platform/middleware/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package middleware

import (
"context"
"log"
"net/http"
"net/url"
"os"
"rpl-service/constants"
"time"

jwtmiddleware "github.com/auth0/go-jwt-middleware/v2"
"github.com/auth0/go-jwt-middleware/v2/jwks"
"github.com/auth0/go-jwt-middleware/v2/validator"
)

// CustomClaims contains custom data we want from the token.
type CustomClaims struct {
Scope string `json:"scope"`
}

// Validate does nothing for this example, but we need
// it to satisfy validator.CustomClaims interface.
func (c CustomClaims) Validate(_ context.Context) error {
return nil
}

// EnsureValidToken is a middleware that will check the validity of our JWT.
func EnsureValidToken() func(next http.Handler) http.Handler {
issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/")
if err != nil {
log.Fatalf("Failed to parse the issuer url: %v", err)
}

provider := jwks.NewCachingProvider(issuerURL, constants.ProviderDuration)

jwtValidator, err := validator.New(
provider.KeyFunc,
validator.RS256,
issuerURL.String(),
[]string{os.Getenv("AUTH0_AUDIENCE")},
validator.WithCustomClaims(
func() validator.CustomClaims {
return &CustomClaims{}
},
),
validator.WithAllowedClockSkew(time.Minute),
)
if err != nil {
log.Fatalf("Failed to set up the jwt validator")
}

errorHandler := func(w http.ResponseWriter, _ *http.Request, err error) {
log.Printf("Encountered error while validating JWT: %v", err)

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusUnauthorized)
_, writingError := w.Write([]byte(`{"message":"Failed to validate JWT."}`))
if writingError != nil {
return
}
}

middleware := jwtmiddleware.New(
jwtValidator.ValidateToken,
jwtmiddleware.WithErrorHandler(errorHandler),
)

return func(next http.Handler) http.Handler {
return middleware.CheckJWT(next)
}
}
7 changes: 6 additions & 1 deletion platform/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"rpl-service/config"
"rpl-service/platform/authenticator"
)

// New registers the routes and returns the router.
func New() *gin.Engine {
func New(_ *authenticator.Authenticator, db *gorm.DB) *gin.Engine {
router := gin.Default()

// To store custom types in our cookies,
Expand All @@ -18,5 +21,7 @@ func New() *gin.Engine {
store := cookie.NewStore([]byte("secret"))
router.Use(sessions.Sessions("auth-session", store))

config.InitializeRoutes(router, db)

return router
}

0 comments on commit b278063

Please sign in to comment.