How to handle User (needed for almost all code)

How do you pass around the authenticated User struct in your Go web applications?

The authentication happened in a middleware, and the User is needed in almost all
code which handles requests.

I could store it in the Context, but somehow I don’t like this, since this storage is not statically typed.

Or I add a parameter to almost every method.

How do you handle this?

2 Likes

one way to do this is to, in an early handler stuff the authenticated user into the requests’s Context and then pull it out again in each handler.

I generally store it in context myself. And I agree it’s not perfect and you have to be careful when dealing with security stuff using non statically typed storage (things like zero values and typos can get you). Generally speaking, most of my APIs use JWTs for auth. So I generally have a middleware like MustBeAuthorized() which validates my JWT against signing secret and ensures the user is authorized. This, generally speaking, also sets a UserID in the context (that is usually the only thing I need in the context, since things like IsAdmin would be validated by another middleware).

So, to ensure a typo on a handler func can’t introduce a bug I generally just wrap the parts prone to typos in a global helper func that I then use everywhere:

// To avoid typos here...
func MyHandler(w http.ResponseWriter, r *http.Request) {
	userID := r.Context().Value("UserID").(int)
}

// ... do this
func MoreCleverHandler(w http.ResponseWriter, r *http.Request) {
	userID := GetUserID(r)
}

func GetUserID(r *http.Request) int {
	// TODO: handle errors, etc.
	return r.Context().Value("UserID").(int)
}

Then you can test the heck out of your GetUserID (or whatever) function and have faith that it will work. Is it perfect? No - but I prefer it slightly over adding the param to every function (e.g. MyHandler(w http.ResponseWriter, r *http.Request, userID int). I guess if I was going that route I’d make it more generic / flexible like MyHandler(w http.ResponseWriter, r *http.Request, c TypedContext) so I could add whatever other fields to TypedContext I cared about.

Anyway, I’d be interested to hear what other people are doing and what has worked well for them.