Search code examples
godesign-patternsweb-applicationsmiddleware

How to inject a repo or service in a middleware in a clean way?


I'm currently working on a web application and implementing a user authentication system using middleware in Go. I'm wondering what the best way is to inject a repository or service in a middleware for user authentication.

The reason I want to do this is because I want to use a repository's method for every user authentication. However, I'm not sure if it's clean to inject a repository or service in a middleware.

Does anyone have any suggestions or best practices for how to do this in a clean and maintainable way?

I have read this question on Stackoverflow but it seems to be related to C# and not necessarily a design issue.

Thanks in advance for your insights.


Solution

  • There are maybe more than a hundred ways to answer to your question, but I'll try to make an answer based on my experiences. First, if I understood your question, you need to know how to inject a repository on a middleware then after, to know if it's a good practice.

    About your first point:

    type AuthenticationRepository interface {
        GetApiKey(location string) (string, error)
    }
    
    func AuthorizationMiddleware(authRepo AuthenticationRepository) gin.HandlerFunc {
        return func(c *gin.Context) {
            // Get the location and key from headers
            location := c.GetHeader("Host")
            apiKey := c.GetHeader("Key")
    
            // Use the repository to get the API key from location
            expectedKey, err := authRepo.GetApiKey(location)
            if err != nil {
                log.Errorf("Unexpected error when trying to get api key: %v", err)
                c.AbortWithStatus(http.StatusServiceUnavailable)
                return
            }
    
            if expectedKey != apiKey {
                c.AbortWithStatus(http.StatusUnauthorized)
                return
            }
    
            // Call the next handler
            c.Next()
        }
    }
    

    With this basic example, you can do what you need on your repository.

    Now about your second point, is it a good practice to do that? If you have only few users or services, yes, you will not have problems. But, if you want to have a scalable infrastructure with a lot of users: no, this is not a good practice.

    Why? Because all your services must retrieve information from your repository. So you are introducing a SPOF and a lot of latency on all your endpoints. You can do some caching, but it will be hard to manage if you have a large amount of users.

    To handle this, you will need to know the difference between authentication and authorization: authentication verifies the identity of a user or service, and authorization determines their access rights.

    So, for example, you can authenticate a user with his username/password (or a service with his client_id/client_secret), then, after authentication your authorization service will provide a JWT token. This token is signed and can be verified by any service that have the corresponding public key. So you don't need to reach a specific service/storage from a repository on every request, because with this public key, you can authorize access requests (depending on additional verified token claims).

    Some documentation about JWT and flow to get it:

    So, all of that depend on the volume of users :)