Authentication and authorization

Authentication is the process of associating an API request with a specific user, while authorization determines if the user has permission to perform the requested operation.

Authentication

A system like this has to consider the needs of programmatic clients ( like integrations into other systems ) and the needs of “actual users” ( in this case people logged in to the web interfaces ).

The MFL API server supports both session ( cookie based ) and OAuth 2 ( token based ) authentication. For both approaches, the production API server must be run over HTTPS.

Session Authentication

Logging in

POST the credentials to /api/rest-auth/login/. The payload should be similar to the example below:

{
    "username": "hakunaruhusa@mfltest.slade360.co.ke",
    "password": "hakunaruhusa"
}

A successful login will have a HTTP 200 OK response. The response payload will have a single key parameter: a Django Rest Framework TokenAuthentication key. For example:

{
    "key": "f9a978cd00e9dc0ebfe97d633d98bde4b35f9279"
}

Note

Please note that the username is actually an email address.

Note

We discourage the use of token authentication. Kindly see the section on OAuth2 below.

Logging out

Send an empty ( no payload ) POST to /api/rest-auth/logout/.

A successful logout will get back a HTTP 200 OK response, and a success message similar to the one below:

{
    "success": "Successfully logged out."
}

Getting user details after login

After a user is logged in, a typical client ( such as a web application ) will need to get additional information about the user. This additional information includes permissions.

If the user is logged in, a GET to /api/rest-auth/user/ will get back a HTTP 200 OK response and a user details payload similar to this example:

{
    "id": 3,
    "short_name": "Serikali",
    "full_name": "Serikali Kuu ",
    "all_permissions": [
        "common.add_town",
        "oauth2_provider.change_accesstoken",
        "mfl_gis.delete_wardboundary",
        "auth.add_permission",
        "chul.change_approvalstatus",
        "facilities.delete_facilitytype",
        // a long list of permissions; truncated for brevity
    ],
    "user_permissions": [],
    "groups": [],
    "last_login": "2015-05-04T16:33:36.085065Z",
    "is_superuser": true,
    "email": "serikalikuu@mfltest.slade360.co.ke",
    "first_name": "Serikali",
    "last_name": "Kuu",
    "other_names": "",
    "username": "serikalikuu",
    "is_staff": true,
    "is_active": true,
    "date_joined": "2015-05-03T02:39:03.440962Z",
    "is_national": true,
    "requires_password_change": false
}

If the user is not logged in, the return message will be a HTTP 403 FORBIDDEN with the following message:

{
    "detail": "Authentication credentials were not provided."
}

Note

If a user needs to change their password e.g because it was created by an admin and must be changed on first login, the requires_password_change boolean property will be set to true.

Every well behaved web client should observe this property and implement the appropriate “roadblock”.

OAuth2 Authentication

You can learn all that you need to know about OAuth2 by reading rfc6749.

A simple OAuth2 workflow

If you are in too much of a hurry to read all that, here is what you should do:

Registering a new “application”

You should know the user ID of the user that you’d like to register an application for. You can obtain that ID from the user details API described above or from /api/users/.

You need to know the authorization_grant_type that you’d like for the new application. For the example below, we will use password. If you do not know what to choose, read rfc6749 .

The next decision is the choice of client_type. For the example below, we will use confidential. As always - consult rfc6749 for more context.

POST to /api/users/applications/ a payload similar to this example:

{
    "client_type": "confidential",
    "authorization_grant_type": "password",
    "name": "Demo / Docs Application",
    "user": 3
}

A successful POST will get back a HTTP 201 CREATED response, and a representation of the new application. This example request got back this representation:

{
    "id": 1,
    "client_id": "<redacted>",
    "redirect_uris": "",
    "client_type": "confidential",
    "authorization_grant_type": "password",
    "client_secret": "<redacted>",
    "name": "Demo / Docs Application",
    "skip_authorization": false,
    "user": 3
}

Note

  • The client_id and client_secret fields were automatically assigned.
  • The skip_authorization and redirect_urls fields have default values.
  • A single user can be associated with multiple applications.
Authenticating using OAuth2 tokens

First, obtain an access token by POST ing the user’s credentials to /o/token/. For example:

curl -X POST -d "grant_type=password&username=serikalikuu@mfltest.slade360.co.ke&password=serikalikuu" http://sfzgvKKVpLxyHn3EbZrepehJnLn1r0OOFnuqBNy7:7SMXKum5CJVWABxIitwszES3Kls5RTBzYzJDI5jdvgPcw0vSjP5pnlYHfANSkPyn8pzSfyi5ETesPGXbbiKih0D3YRjE49IlsMShJy0p6pxLOLp72UKsNKxnj08H0fXP@localhost:8000/o/token/

Which breaks down as:

curl -X POST -d grant_type=<grant_type>&username=<email>&password=<password>" http://<client_id>:<client_secret>@<host>:<por>/o/token/

If you authenticate successfully, the reply from the server will be a JSON payload that has the issued access token, the refresh token, the access token type, expiry and scope. For example:

{
    "access_token": "fKDvh2fFLR1iFPuB26RUEalbjYO4rx",
    "token_type": "Bearer",
    "expires_in": 36000,
    "refresh_token": "jLwpCh3WbOXBeb01XMeZR5AQYedkj1",
    "scope": "read write"
}

Pick the access_token and send it in an Authorization: Bearer header e.g

curl -H "Authorization: Bearer ziBLqoXwVEA8lW9yEmE260AZ4lCJHq" http://localhost:8000/api/common/counties/

Authorization

This server’s Role Based Access Control setup is based on the Django framework permissions and authorization system.

Understanding the role based access control setup

The user details API endpoint ( explained above ) returns the logged in user’s permissions.

A user’s permissions come from three “sources”:

  • the permissions assigned to the group ( role ) that the user belongs to
  • the permissions assigned directly to the user
  • the is_superuser boolean flag; a user who is a “superuser” automatically gets all permissions

The MFL API server has an additional “layer” of authorization: whether a user is a “national user” or a “county user”. In certain list endpoints ( chiefly those that deal directly with facilities ), a “county” user will have their results limited to facilities that are located in their county.

Note

This API server does not support “true” unauthenticated read-only access For the public site, OAuth2 credentials ( that correspond to a role with limited access ) will be used.

Note

From the point of view of the MFL API, regulator systems are just one more set of API clients.

Users and counties

In 2010, Kenya got a new constitution. One of the major changes was the establishment of a devolved system of government.

The second generation MFL API ( this server ) is designed for the era of devolution. In this system, facility record management should occur at the county level.

The separation of privileges between data entry staff ( “makers” ) and those responsible for approval ( “checkers” ) can be modelled easily using the role based access control setup described above.

The only additional need is the need to link county level users to counties, and use that information to limit their access. This has been achieved by adding an is_national boolean flag to the custom user model and adding a resource that links users to counties. The example user resource below represents a non-national ( county ) user ( note the is_national field ):

{
    "id": 4,
    "short_name": "Serikali",
    "full_name": "Serikali Ndogo ",
    "all_permissions": [
        "common.add_town",
        // many more permissions
    ],
    "user_permissions": [],
    "groups": [],
    "last_login": null,
    "is_superuser": true,
    "email": "serikalindogo@mfltest.slade360.co.ke",
    "first_name": "Serikali",
    "last_name": "Ndogo",
    "other_names": "",
    "username": "serikalindogo",
    "is_staff": true,
    "is_active": true,
    "date_joined": "2015-05-03T02:39:03.443301Z",
    "is_national": false
}

In order to link a user to a county, you need to have two pieces of information:

  • the user’s id
  • the county’s id - easily obtained from /api/common/counties/

With these two pieces of information in place, POST to /api/common/user_counties/ a payload similar to this example:

{
    "user": 4,
    "county": "d5f54838-8743-4774-a866-75d7744a9814"
}

A successful operation will get back a HTTP 201 CREATED response and a representation of the newly created resource. For example:

{
    "id": "073d8bfa-2a86-4f9a-9cbe-0b8ac6780c3a",
    "created": "2015-05-04T17:44:56.441006Z",
    "updated": "2015-05-04T17:44:56.441027Z",
    "deleted": false,
    "active": true,
    "created_by": 3,
    "updated_by": 3,
    "user": 4,
    "county": "d5f54838-8743-4774-a866-75d7744a9814"
}

The filtering of results by county is transparent ( the API client does not need to do anything ).

Note

A user can only have one active link to a county at any particular time. Any attempt to link a user to more than one county at a time will get a validation error.

If you’d like to change the county that a user is linked to, you will need to first inactivate the existing record ( PATCH it and set active to false ).

In order to determine the role that a user is currently linked to, issue a GET similar to /api/common/user_counties/?user=4&active=true. In this example, 4 is the user’s id.

Setting up users, permissions and groups

Permissions

API clients should treat permissions as “fixed” builtins. The server does not implement any endpoint that can be used to add, edit or remove a permission.

The available permissions can be listed by issuing a GET to /api/users/permissions/. The results will look like this:

{
    "count": 216,
    "next": "http://localhost:8000/api/users/permissions/?page=2",
    "previous": null,
    "results": [
        {
            "id": 61,
            "name": "Can add email address",
            "codename": "add_emailaddress",
            "content_type": 21
        },
        {
            "id": 62,
            "name": "Can change email address",
            "codename": "change_emailaddress",
            "content_type": 21
        },
        {
            "id": 63,
            "name": "Can delete email address",
            "codename": "delete_emailaddress",
            "content_type": 21
        },
        // truncated for brevity
    ]
}

Note

The content_type keys in the example above originate from Django’s contenttypes framework. For an API consumer, they are an implementation detail / curiosity; API clients will nto need to know more about them.

Groups

The API server provides APIs that can be used to create roles, alter existing roles and retire roles.

Existing roles ( groups ) can be listed by issuing a GET to /api/users/groups/.

Creating a new role

POST to /api/users/groups/ a payload that similar to the one below:

{
    "name": "Documentation Example Group",
    "permissions": [
        {
            "id": 61,
            "name": "Can add email address",
            "codename": "add_emailaddress"
        },
        {
            "id": 62,
            "name": "Can change email address",
            "codename": "change_emailaddress"
        }
    ]
}

A successful operation will get back a HTTP 201 CREATED status.

Note

You must supply both a name and permissions.

Updating an existing role

PUT or PATCH to a group detail URL e.g /api/users/groups/1/.

For example, to take away from the example role the “Can change email address” permission, the following PATCH request should be sent:

{
    "permissions": [
        {
            "id": 61,
            "name": "Can add email address",
            "codename": "add_emailaddress"
        }
    ]
}

A similar approach will be followed to add permissions.

A successful operation will get back a HTTP 200 OK status.

Note

Permissions will always be overwritten when you perform an update.

User management

User registration ( sign up )

POST to /api/rest-auth/registration/ a payload similar to this example:

{
    "username": "likeforreal",
    "email": "likeforreal@yodawg.dawg",
    "password1": "most_secure_password_in_the_world_like_for_real",
    "password2": "most_secure_password_in_the_world_like_for_real"
}

A successful operation will get back a HTTP 201 CREATED response and a representation of the new user. For example:

HTTP 201 CREATED
Content-Type: application/json
Vary: Accept
Allow: POST, OPTIONS, HEAD

{
    "id": 9,
    "short_name": "",
    "full_name": "  ",
    "all_permissions": [],
    "user_permissions": [],
    "groups": [],
    "last_login": "2015-05-05T09:12:01.888514Z",
    "is_superuser": false,
    "email": "likeforreal1@yodawg.dawg",
    "first_name": "",
    "last_name": "",
    "other_names": "",
    "username": "likeforreal1",
    "is_staff": false,
    "is_active": true,
    "date_joined": "2015-05-05T09:12:01.790167Z",
    "is_national": false
}

Note

This API server does not implement email address confirmation. A future release might implement that.

Note

The registration operation described above suffices, for public users.

The manner in which users should be linked to counties has already been discussed in the Authorization section.

Linking users to groups

In order to assign a user to a group, you will need to know the group ID ( which you can obtain from /api/groups/ ).

PATCH an already existing user with a payload similar to this example:

{
    "groups": [
        {"id": 1, "name": "Documentation Example Group"}
    ]
}

In order to remove them from their assigned roles, PATCH with an empty groups list.

Note

This server does not support the direct assignment of permissions to users. That is deliberate.

Updating user details

Every writable attribute of a user record can be PATCH``ed. For example, to inactivate or retire a user, ``PATCH the user’s ( detail ) record and set is_active to false.

For example: if the detail record for the user we registered above ( likeforreal ) is to be found at /api/users/9/, the user can be inactivated by PATCH ing /api/users/9/ with:

{
    "active": false
}

Note

The same general approach can be used for any other flag e.g is_superuser.

Password changes

The password of the logged in user can be changed by making a POST to /api/rest-auth/password/change/ a payload similar to this example:

{
    "old_password": "oldanddonewith",
    "new_password1": "newhotness",
    "new_password2": "newhotness"
}

Note

A future version of this server may add support for social authentication e.g login via Facebook, Twitter or Google accounts.