Is your feature request related to a problem? Please describe.
We currently don't have an authentication and authorisation system in the toolbox. We are building a GraphQL where there is currently no access control, but in the future, once the api is ready, it will be used by different people having different roles in a shared workflow and we need to implement some security rules.
Describe the solution you'd like
We would need to discuss how we want to implement this. I think it is not impossible to support different identity providers such as Auth0 or even build our own authentication mechanism. The advantage of having our own mechanism is that we have less moving parts, it is easier to test and manage as a whole, although the security is obviously not comparable to what Auth0 or Cognito are able to offer.
1. Some ideas for a permissions model
Table users
Column
DataType
Comments
id
int
primary key
name
String
full name
Table credentials
We could imagine a given user could have more than one kind of credentials. Also, if we decide to delegate authentication to Auth0, then this table would not be needed. But we would still need a table of known users in the toolbox, that's why I am suggesting that we keep credentials separate.
Column
DataType
Comments
id
int
primary key
user_id
int
foreign-key
kind
enum
email-password, phone
data
jsonb
depending on the kind
Table roles:
Column
DataType
Comments
id
int
primary key
name
string
Example of roles:
da_admin: DistributeAid admins
group_admin: Aid Group admins
volunteer: Aid Group volunteer
Table permissions:
We can mimic the way Auth0 models permissions:
Column
DataType
Comments
id
int
primary key
role_id
int
foreign key
permission
string
{action}:{resource}:{opts}
Examples:
create:group: this gives the permission to a user to create an aid group.
update:group:author: this would give the permission to a user to only update a group that he/she has previously created
Table user_roles:
Column
DataType
Comments
id
int
primary key
group_id
int
foreign key
user_id
int
foreign key
role_id
int
foreign key
Notes:
a user belongs to a group if she/he has a least one role in that group. This could be a quite flexible way of modelling user memberships. This way, a user could have different roles in different aid groups. From what I can see, Auth0 does not support this.
only users with da_admin role can administrate roles, permissions, groups. They can add/remove themselves from existing groups.
We might need to implement some built-in permissions to prevent the system from being accidentally hijacked.
The bottom line is that we could have a set of permissions per user and per group. The total list of permissions is the union of all permissions of all the roles a given user has in a given group.
This could be allowed by permission create:user under the da_admin role.
We would need to create a new entry in our internal users table, and get the generated user id.
Then, if we decide we want to user our own authentication mechanism we could insert a new entry in table credentials, in the same transaction.
Alternatively, if we decide we want to use Auth0, we can create a new user via the management api. The request would include our internal user id. This way the user in Auth0 is linked to our internal user.
If we decide to issue opaque tokens, then we will need to store them in a tokens table, and link them to a valid user:
Column
DataType
Comments
id
int
primary key
token
string
unique
user_id
foreign key
created
date time
for expirations
7. How do we authenticate user requests
Tokens obtained via login, or externally via third party providers (auth0) need to be part of the request (authorisation header).
We could support multiple types of tokens:
opaque tokens
jwt issued by Toolbox (login mutation)
jwt issued by Auth0
8. How do we authorise requests with a JWT
Since it is a JWT, we need to verify the signature
Then extract the claims
The token is self contained, so any permissions will be encoded in those claims
The claims should contain a user_id the toolbox knows about.
Note: we can encode user membership to groups, via their roles, or we can encode all the permissions. However as a user belongs to an increasing number of groups, this might not scale very well and result in very long claims.
9. How do we authorise requests with an opaque token
We need to resolve the token, check it is present in the database and is linked to a valid user
Then build the list of permissions for that user depending on that user permissions. Also depending on the action and the resource being impacted by the request, we are interested in just a subset of all permissions for that user.
11. How a user joins a group
We could implement a simple mutation:
type JoinRequest {
user: User!,
group: Group!,
status: [pending, accepted, rejected, expired],
createdAt,
updatedAt
}
mutation {
joinGroup(group: id) {
id,
group {
id
},
user {
id
},
status
}
}
12. How a group admin accepts a join request
We could implement a mutation such as:
mutation {
updateJoinGroupRequest(id: "...", status: "accepted") {
id,
group {
id
},
user {
id
},
status
}
}
By accepting a request, a new role is created for that user for that group. The presence of that role defines the membership of that user in that role.
13. How do we scope range queries
We will need to protect every query that returns a range of resources, eg. get all groups, get all shipments, and return only those resources the user has actually access to. This needs further investigation.
Is your feature request related to a problem? Please describe.
We currently don't have an authentication and authorisation system in the toolbox. We are building a GraphQL where there is currently no access control, but in the future, once the api is ready, it will be used by different people having different roles in a shared workflow and we need to implement some security rules.
Describe the solution you'd like
We would need to discuss how we want to implement this. I think it is not impossible to support different identity providers such as Auth0 or even build our own authentication mechanism. The advantage of having our own mechanism is that we have less moving parts, it is easier to test and manage as a whole, although the security is obviously not comparable to what Auth0 or Cognito are able to offer.
1. Some ideas for a permissions model
We could imagine a given user could have more than one kind of credentials. Also, if we decide to delegate authentication to Auth0, then this table would not be needed. But we would still need a table of known users in the toolbox, that's why I am suggesting that we keep credentials separate.
email-password
,phone
Example of roles:
da_admin
: DistributeAid adminsgroup_admin
: Aid Group adminsvolunteer
: Aid Group volunteerWe can mimic the way Auth0 models permissions:
{action}:{resource}:{opts}
Examples:
create:group
: this gives the permission to a user to create an aid group.update:group:author
: this would give the permission to a user to only update a group that he/she has previously createdNotes:
da_admin
role can administrate roles, permissions, groups. They can add/remove themselves from existing groups.The bottom line is that we could have a set of permissions per user and per group. The total list of permissions is the union of all permissions of all the roles a given user has in a given group.
2. How do we create new users
We could have a new simple GraphQL mutation:
Notes:
create:user
under theda_admin
role.users
table, and get the generated userid
.credentials
, in the same transaction.3. How a volunteer could register into the platform
We could have a new simple GraphQL mutation
where
success
is a boolean value.4. How a user can ask to receive a password reset link
We could have a new simple GraphQL mutation
This would send a reset link with a token via email. Note: we need to integrate with an email provider first.
5. How a user can reset their password
We could have a new simple GraphQL mutation
Notes:
6. How we authenticate users
We can have a simple mutation that creates a new token:
If we decide to issue opaque tokens, then we will need to store them in a
tokens
table, and link them to a valid user:7. How do we authenticate user requests
Tokens obtained via login, or externally via third party providers (auth0) need to be part of the request (
authorisation
header).We could support multiple types of tokens:
8. How do we authorise requests with a JWT
user_id
the toolbox knows about.Note: we can encode user membership to groups, via their roles, or we can encode all the permissions. However as a user belongs to an increasing number of groups, this might not scale very well and result in very long claims.
9. How do we authorise requests with an opaque token
11. How a user joins a group
We could implement a simple mutation:
12. How a group admin accepts a join request
We could implement a mutation such as:
By accepting a request, a new role is created for that user for that group. The presence of that role defines the membership of that user in that role.
13. How do we scope range queries
We will need to protect every query that returns a range of resources, eg. get all groups, get all shipments, and return only those resources the user has actually access to. This needs further investigation.