Authorization
While authentication is handled by the outer API layer, authorization can, and indeed should, be handled by our domain.
Authorization can be considered business logic, and that's what our domain is all about, business logic! Our domain describes when actions should and should not be allowed, and those decisions can depend on user attributes as well, such as id or role.
Say we have an action that creates and updates widgets. One very common bit of authorization is that regular users can only update their own widgets, where as admins can update all widgets.
Let's write this:
actions.js
1
const actions = {
2
createWidget: (state, payload) => {
3
const { user } = payload
4
if (!user) throw new Error('userMissing')
5
6
return [{
7
type: 'WidgetCreated',
8
user: payload.user,
9
at: Date.now(),
10
}]
11
},
12
13
updateWidget: (state, payload) => {
14
const { user } = payload
15
if (!user) throw new Error('userMissing')
16
if (user.role !== 'admin' && user.id !== state.userId)
17
throw new Error('unauthorized')
18
// ^ authorization logic
19
20
return [{
21
type: 'WidgetUpdated',
22
user: payload.user,
23
at: Date.now(),
24
}]
25
},
26
}
27
28
module.exports = actions
Copied!
reducer.js
1
const initialState = {}
2
3
const reducer = (state, event) => {
4
const {
5
user,
6
at,
7
} = event
8
9
switch (event.type) {
10
case 'WidgetCreated':
11
return {
12
userId: user.id,
13
updatedAt: at,
14
}
15
16
case 'WidgetUpdated':
17
return {
18
updatedAt: at,
19
}
20
21
default:
22
return state
23
}
24
}
25
26
module.exports = (events, state=initialState) => events.reduce(reducer, state)
Copied!
What are we doing here?
    1.
    Let's start with the createWidget action. We check to make sure the payload contains a user object and if it does, we make sure to include the user in the generated event.
    2.
    Then in the reducer - whenever we receive a WidgetCreated event, we store the userId so we can refer back to it later.
    3.
    Finally, whenever the updateWidget action is called, we check to make sure that the caller's id matches the userId we stored earlier (or if caller is an admin) and if not, we throw an unauthorized error.
Very simple authorization logic and it fits very neatly into our domain.
Last modified 2yr ago
Copy link