Authentication and Authorization in DDD

A common scenario in an application is deal with authentication and authorization. 
As I’m a great enthusiast of Domain-Driven Design, I wonder what are the best approaches to use authentication/authorization in DDD.
In the following lines I show two approaches based on an imaginary use case.

I am eager to hear your comments and suggestions.

Prerequisites

Concepts that would be good to know:

Use Case example

Imagine we want to develop the next blogging platform.

We define the Ubiquitous Language in the context of the next blogging platform as below:

  • Author: existing user in the next blogging platform. It is Entity with unique identifier of authorId;
  • Blog: specific area of blogging platform containing posts. It is Entity with unique identifier of blogId;
  • Collaborator: Author who can create the posts to the given blog;
  • Authentication: process to identify the Author.
  • Authorization: process to check if the Author is Collaborator in the given Blog.

The use case that we have to develop is create a post in a blog where any collaborator of the blog can creates posts.
In order to compare both solutions, the use case can be executed from Web, REST and Console.

Note: the sample code has been simplified (dependencies, getters) in order to focus on important concepts.

Version 1: Authentication/Authorization outside Application Services

Once the request reaches the ApplicationService it is executed without check any authentication or authorization.

ApplicationService

// Request
class AddPostRequest()
{
public function authorId() {…}
public function blogId() {…}
public function title() {…}
public function text() {…}
}
// Application Service
class AddPostService()
{
public function execute($request)
{
$authorId = $request->authorId();
$blogId = $request->blogId();
$title = $request->title();
$text = $request->text();
       if (!$blog = $this->blogRepository->blogOfId($blogId)) {
throw new BlogNotFoundException();
}

$post = $blog->buildPost(
$this->postRepository->nextIdentity(),
$authorId,
$title,
$text
);
       $this->postRepository->save($post);
}
}

Web

From controller (or from some hook of the framework), before the use case is executed, it checks if current user is authenticated and also if he is a collaborator of the blog.

In order to checks these things, we use DomainServices in the controller:


class PostController
{
public newPostAction()
{
if (!$this->sessionService()->isAuthenticated()) {
throw new UnauthenticatedUserException();
}
       $blogId = $this->request->get(‘blog_id’);
$userId = $this->sessionService()->currentUserId();

$isCollaborator = $this->permissionService()->isCollaborator(
$userId, $blogId
);
       if (!$isCollaborator) {
throw new UnauthorizedUserException();
}
       // executes AddPostService use case
}
}

REST

It uses header Authorization to get a token. It checks that this token belong to a user and also that this user is a collaborator in the blog.

In order to checks these things, we use DomainServices in the controller:

class PostRestFulController
{
public newPostAction()
{
$authorizationValue = $this->authorizationHeader();
$userId = $this->oauthService()->userId($authorizationValue);
$blogId = $this->request->get(‘blog_id’);
       if (!$userId) {
throw new UnauthenticatedUserException();
}
       $isCollaborator = $this->permissionService()->isCollaborator(
$userId, $blogId
);
       if (!$isCollaborator) {
throw new UnauthorizedUserException();
}
       // execute AddPostService use case
}
}

Console

It executes the use case without checks authentication/authorization. userId comes from command line as parameter.

> blog add-post — user-id 1 — blog-id 1 — title Foo …
class PostConsole
{
public newPostCommand()
{
// gets values from console parameters
// execute AddPostService use case
}
}

Sum up

  • Authentication/Authorization is checked outside from ApplicationService in the Controller (or in a hook in the framework).
  • Authentication/Authorization must be executed before ApplicationService use case.
  • Depending on entry point of the application an authentication service is uses. In the example OAuthService, SessionService.

Version 2: Authentication/Authorization within Application Services

Following Vernon’s example in his book IDDD and CollaboratorService, we can use the language inside the use case where authentication/authorization is executed implicitly.

ApplicationService

It uses a DomainService named CollaboratorService that checks authentication and authorization behind the scenes. CollaboratorService returns Author ValueObject and its implementation may reach another bounded context.

interface CollaboratorService
{
public function authorFrom($authorId, $blogId);
}
// Request
class AddPostRequest
{
public function authorId() {…}
public function blogId() {…}
public function title() {…}
public function text() {…}
}
// Application Service
class AddPostService
{
public function execute($request)
{
$authorId = $request->authorId();
$blogId = $request->blogId();
$title = $request->title();
$text = $request->text();
       $author = $this->collaboratorService->authorFrom(
$authorId,
$blogId
);
       if (!$author) {
throw new InvalidAuthorException();
}
       if (!$blog = $this->blogRepository->blogOfId($blogId)) {
throw new BlogNotFoundException();
}

$post = $blog->buildPost(
$this->postRepository->nextIdentity(),
$author,
$title,
$text
);

$this->postRepository->save($post);
}
}

Web

Controller doesn’t need the check authentication/authorization. However to create the use case request and pass authorId parameter a common scenario is to read the userId from session. The rest of params comes from http request. 
Then the implementation of CollaboratorService works directly with the userId value.

class UserIdCollaboratorService implements CollaboratorService
{
public function authorFrom($authorId, $blogId) { … }
}
class PostController
{
public newPostAction()
{
$authorId = $this->sessionService()->userId();
        // executes ApplicationService use case
}
}

REST

It uses http header Authorization to get the token of the user. When the entry point to the application is REST, the authentication mechanism could be OAuth, JWT… then the implementation CollaboratorService should works with that token.

class OAuthCollaboratorService implements CollaboratorService
{
public function authorFrom($authorId, $blogId) { … }
}
class PostRestFulController
{
public newPostAction()
{
$authorId = $this->authorizationHeader();
         // executes ApplicationService use case
}
}

Console

authorId param comes from command line, so if the given author-id belongs to blog then no exceptions will be thrown:

blog add-post — author-id 1 — blog-id 1 — title Foo

The implementation of CollaboratorService could be the same as we use in Web or a specific implementation.

class ConsoleCollaboratorService implements CollaboratorService
{
public function authorFrom($authorId, $blogId) { … }
}
class PostConsole
{
public newPostCommand()
{
// gets values from console parameters
// executes AddPostService use case
}
}

Sum up

  • All checks of authentication/authorization are within the ApplicationService implicitly used in CollaboratorService.
  • The input parameter in authorId in AddPostService is somehow polymorphic, due accepts tokens from REST or direct id value from Web. Maybe this is the key that allow us to put authentication/authorization within ApplicationService.
  • CollaboratorService needs several implementation depending on authentication mechanism.

Changelog

  • 2016–01–08: added Ubiquitous Language description. Thanks to @robegrei