marcusolsson / goddd

Exploring DDD in Go
MIT License
2.41k stars 273 forks source link

Idiomatic way of attaching authentication #17

Closed valsor closed 8 years ago

valsor commented 8 years ago

What's an idiomatic way to add authentication to goddd?

I think I should perform following operation for each request:

After that each endpoint can have different middleware for authorization such RequireAdmin, RequireLogin, RequireRole etc. Should this functionality be added as another application service? Or as infrastructure service like logging? Maybe another way? How can I add a middleware that handle every request?

marcusolsson commented 8 years ago

This is something I've been meaning to take a closer look at, so I'm excited to see you post!

Currently, the domain does not have any concept of users (although I could easily see a requirement for being able to see who reported the incident or booked the cargo). My take is that any domain concepts in the context object should be extracted and passed to an application service (as opposed to sending the context to every service method). E.g.

AssignCargoToRoute(id cargo.TrackingID, itinerary cargo.Itinerary, assignedBy user.ID) error

If the user ID was a part of the domain, my first attempt would be to decode the user ID into a UserID field in the request struct, or possibly extracting it from the context obj in the makeEndpoint functions.

If we're to use the token only to authorize the API call, then I would consider writing a endpoint.Middleware where I'd decode the token and continue only if it was valid.

valsor commented 8 years ago

Thank you for comprehensive and quick answer.

valsor commented 8 years ago

User is not supposed to be a part of domain. Then if I use token with user ID for authorization, every endpoint and every request use the same authorization logic, should I repeat the middleware code in every application service? Wouldn' itt be idiomatic to have some common/shared middleware that serves every request? Where should we place such middleware? By the way I use alternative layout with domain object in root dir.

marcusolsson commented 8 years ago

Yeah, I could see a jwt subpackage providing the middleware functionality. There's also a PR to add this to go-kit as well, that might serve as inspiration https://github.com/go-kit/kit/pull/335

valsor commented 8 years ago

Thanks. I'll check it.

dkushner commented 7 years ago

@valsor, not sure if this helps at all, but I've taken "authentication" to be its own domain package including its own service and middleware modules that are then referenced by (generally) the endpoint module of each domain package that requires these features.

You mention a few things that I may be able to address, and understand that I'm a bit of a Go neophyte and not an expert on DDD so someone with more experience please correct me if I stray from the light:

User is not supposed to be a part of domain.

If your data model includes a "user" as a basic component, then it is indeed part of your domain. I think what you're concerned about is mixing the domain concept of a "user" with the authentication concept of "the user performing this action". I've skirted this by instead using the concept of a "principal" in my authentication domain. The "principal" abstracts away the details of the true actor in this equation and exposes only two things: a method to uniquely identify the actor and a list of access claims the actor has made.

These primitives seem to jive well with just about every authentication system you may use. The middleware (which is an actual endpoint.Middleware) provided by the authentication package is then only required to parse the token/credentials from the inbound request, retrieve the principal they correspond to and add this information to the context.

The actual invocation looks something like this, when setting up an endpoint in a domain package:

someEndpoint = makeSomeEndpoint(...)
someEndpoint = authentication.Restrict("users:update")(someEndpoint)