Drupal 8, Simple OAuth and simple REST

In this article, I am going to try to build a full picture of how we could set up Drupal 8 to receive REST requests and how exactly the requests could look like if we want to use as much core modules as possible plus the Simple OAuth module.

I am also packaging this article with a GitHub repo that contains a Drupal + Docker package that you could quickly install and either test on or compare with your own version if you are not quite sure about some settings.

The Drupal part

The Drupal part is quite straight forward but needs some contributed modules to get us started. Since we want to authenticate ourselves with OAuth, we could download and install the Simple OAuth module and in order to have the REST settings presented in a visual way we could download and install the REST UI module. The core modules that we have to have activated are HAL, RESTful Web Services and Serialization and if we want to fiddle with file uploads, we should download and install the File Entity module.

Some of the modules that you have to activate.

Starting with the auth

In order to set up our OAuth authentication, after we install the needed modules we would have to go to Configuration > Simple OAuth and finish the installation process by setting the Access token expiration time, the Refresh token expiration time and generating and setting the path to the public and private keys. The keys you could easily generate with the help of OpenSSL terminal command and it is a good idea to have them outside of your Drupal document root as explained on the project’s drupal.org page.

openssl genrsa -out private.key 2048
openssl rsa -in private.key -pubout > public.key
The general Simple OAuth settings.

In our example, of course, the Access token expiration time and the Refresh token expiration time values are ridiculously high, but that is just for demonstration purposes.

Then it is time to set up our client. We could do it from Configuration > WEB SERVICES > Consumers and edit the default client there. The default values are completely fine, just make sure you create a Drupal role before so that we could use it to fine-tune the permissions that we are exposing for our REST API and then select it under scopes (the role I created in this example is called rest). Also, make sure you set up a secret that you are going to remember (in this example it is just abc123).

The rest client settings page.

Some permissions.

So for this example, I created a role called rest. It is important to have all the permissions that make sense for the use case that we have. For example, the rest role should be able to create nodes of type Post. Also after we install the File entity module it is important to give the role all the permissions to create the file entities. If you are not sure about any permission, simply install the example and compare all the permissions that are used there.

The Permissions page in Drupal.

Then we would have to create the actual user that would have this role. In my case, it is a user with name and password both test.

Creating our test user.

Rest UI settings.

As we installed this module, we could use it to allow certain resources and specify how exactly they could be accessed.

The REST UI general page with the activated resources.

We are activating Content, File and File Upload. Content, of course, stands for the Drupal Content Types. That means that we are allowing the users to try to create, update or delete nodes. That is not going to work if the authenticated user does not have a role that also has all these permissions. For example, if we expose the Content POST method we would have to additionally make sure that the user that we would be authenticating with will have the rest role (the one that we created earlier) and that the rest role has the “Post: Create new content” permission in order to be able to create nodes of type Post. It sounds complicated but actually makes a lot of sense an is making everything easy to fine-tune and model the way you need it.

The requests to the REST API

Our Drupal REST API is ready so let’s start with the actual requests. We are going to be using Postman. It is the coolest tool out there to test APIs and makes the whole testing of your Drupal API much much easier.

Authenticating our request

Authenticating is the first and most important step. It makes the whole fetching of data much more reliable, elegant and secure.

We are going to use the Simple Ouath module to authenticate. In order to do that we have to make a POST request to the /oauth/token path of your Drupal instance. As data, we are going to be sending the username and password of a test Drupal user that has the rest role assigned to it, the client secret that we specified in our client settings, grant type password, as client id the UUID of the client that we are using and for the scope the actual name of the role that is also specified under scope in our client settings.

Here is the example request.

url: http://{your_drupal_url}/oauth/token 
type: POST
data:
{
'username': 'test',
'password': 'test',
'client_secret': 'abc123',
'grant_type': 'password',
'client_id': 'b27f3a91-c033-4fa5-adf2-dd06050dcf4b',
'scope': 'rest'
}

And of course, you would have to use your own user and client credentials as explained before.

Authenticating to our Drupal instance with Postman.

As a result, we are getting a response array that contains 3 very important variables and these are token_type, access_token and refresh_token. The first two we are going to be using in the next section in order to authenticate ourselves and the refresh token is used when we are going to request a new token if the access token is expiring. We are not going to cover the refresh token request, but it is well documented here when you need to use it.

Debugging our authentication

After logging in it is time to check if we actually granted all the permissions that we need. We could easily do it by doing a GET request on the /oauth/debug path of our Drupal instance. The most essential part here is to assure the API that we are already authenticated. We are doing that by setting the Authorization header with the token_type value followed by single space and the access_token value that we got from the authentication request that we previously did.

url: http://{your_drupal_url}/oauth/debug?_format=json
type: GET
headers:
{
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjIzYTZhMjMwODYxZGUwYjZkOWExM2I4ZjQ2ZTdmOTIzZjRiNjgwMjU4MjNkNWY3OTYwYmRiMDY0MzEzNmE1YjE5NTEyMTc0OTM0ZWZmOTdlIn0.eyJhdWQiOiJiMWNhZjdjOC0zODI0LTRmYWEtYjNjNi05ODU4ZmVjZTFlMjAiLCJqdGkiOiIyM2E2YTIzMDg2MWRlMGI2ZDlhMTNiOGY0NmU3ZjkyM2Y0YjY4MDI1ODIzZDVmNzk2MGJkYjA2NDMxMzZhNWIxOTUxMjE3NDkzNGVmZjk3ZSIsImlhdCI6MTU1MTA5MTIxMywibmJmIjoxNTUxMDkxMjEzLCJleHAiOjE1NTExOTEyMTEsInN1YiI6IjIiLCJzY29wZXMiOlsicmVzdCIsImF1dGhlbnRpY2F0ZWQiXX0.PMsoUCc48R93lvg7lI3k1Cm2JuGmMjMmxv8HpWfKcEUVSxJYAvHaBv7DlI4EU_CSRQvi7qZBa0TvsdN0kLvp4Fl-T2aUXvAunyxKifTk76VlTuKT34QYcrdGHTLkjnmmCqeski8a8AeAWjDCC0q25_WgLQtSZD9fUHN-brjKl0qttm4r40ItvCif1tN2yg5J8WdTge8hzs46nM4s0_06HKrhTKiVo-YH0BT-3fR49A_l8j9jHdS8m8bCjvWcvOArgHAFVDl-6zXDk-hC43w07b_rUaKEwRDxkJeYVkdn3RZiF1BWEVG9A72gbdhprhKb6W9LxiE8C-1RtiZ0yY4F_Q',
}

As a response, we are going to get a pretty big array and by investigating it we are going to find out if our authentication worked and if we are getting the permissions that we expect to. The most important thing is to get under roles rest listed. That means that our authentication has the role and we would be able to do all the allowed permissions specified for it.

Debugging our Drupal authentication with Postman.

Creating a node

Creating a node is quite easy since we already tested our authentication and we saw how we could do POST requests.

First, in order to create a node, we would have to know what Content Types do we have in our system and if our role has the permission to create new nodes for it. In this example, we have just one Content Type, called Post, with machine name post.

So in order to create a new node we are going to be making a POST request on /node?_format=json and as data, we are going to post _links type, which a special link, containing our Content-Type machine name, the actual Content-Type under type target_id and the title of our new node.

The second most important thing is the headers and here we have exactly the same Authorization header made out of the token_type value followed by single space and the access_token (from the authentication request) and we have also two more headers Content-Type and Accept containing the application format of the response that we would expect.

Posting our request to create a new node.
url: http://{your_drupal_url}/node?_format=json
type: POST
data: {
"_links": {
"type": {"href": "http://{your_drupal_url}/rest/type/node/post"}
},
"type": [{"target_id":"post"}],
"title":[{"value":"Clean clams crammed in clean cans."}]
}
headers:
{
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjIzYTZhMjMwODYxZGUwYjZkOWExM2I4ZjQ2ZTdmOTIzZjRiNjgwMjU4MjNkNWY3OTYwYmRiMDY0MzEzNmE1YjE5NTEyMTc0OTM0ZWZmOTdlIn0.eyJhdWQiOiJiMWNhZjdjOC0zODI0LTRmYWEtYjNjNi05ODU4ZmVjZTFlMjAiLCJqdGkiOiIyM2E2YTIzMDg2MWRlMGI2ZDlhMTNiOGY0NmU3ZjkyM2Y0YjY4MDI1ODIzZDVmNzk2MGJkYjA2NDMxMzZhNWIxOTUxMjE3NDkzNGVmZjk3ZSIsImlhdCI6MTU1MTA5MTIxMywibmJmIjoxNTUxMDkxMjEzLCJleHAiOjE1NTExOTEyMTEsInN1YiI6IjIiLCJzY29wZXMiOlsicmVzdCIsImF1dGhlbnRpY2F0ZWQiXX0.PMsoUCc48R93lvg7lI3k1Cm2JuGmMjMmxv8HpWfKcEUVSxJYAvHaBv7DlI4EU_CSRQvi7qZBa0TvsdN0kLvp4Fl-T2aUXvAunyxKifTk76VlTuKT34QYcrdGHTLkjnmmCqeski8a8AeAWjDCC0q25_WgLQtSZD9fUHN-brjKl0qttm4r40ItvCif1tN2yg5J8WdTge8hzs46nM4s0_06HKrhTKiVo-YH0BT-3fR49A_l8j9jHdS8m8bCjvWcvOArgHAFVDl-6zXDk-hC43w07b_rUaKEwRDxkJeYVkdn3RZiF1BWEVG9A72gbdhprhKb6W9LxiE8C-1RtiZ0yY4F_Q',
'Content-Type': 'application/hal+json',
'Accept': 'application/hal+json'
}

Retrieving a node

Getting the node is logically enough being made with a GET request. Before that, you would have to know the id of a node that already exists in the system. In the example below, it is getting the node with id 1. Further, you would also have to include the Authorization headers in order to get the permissions to retrieve the node.

url: http://{your_drupal_url}/node/1?_format=json
type: GET
headers:
{
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjIzYTZhMjMwODYxZGUwYjZkOWExM2I4ZjQ2ZTdmOTIzZjRiNjgwMjU4MjNkNWY3OTYwYmRiMDY0MzEzNmE1YjE5NTEyMTc0OTM0ZWZmOTdlIn0.eyJhdWQiOiJiMWNhZjdjOC0zODI0LTRmYWEtYjNjNi05ODU4ZmVjZTFlMjAiLCJqdGkiOiIyM2E2YTIzMDg2MWRlMGI2ZDlhMTNiOGY0NmU3ZjkyM2Y0YjY4MDI1ODIzZDVmNzk2MGJkYjA2NDMxMzZhNWIxOTUxMjE3NDkzNGVmZjk3ZSIsImlhdCI6MTU1MTA5MTIxMywibmJmIjoxNTUxMDkxMjEzLCJleHAiOjE1NTExOTEyMTEsInN1YiI6IjIiLCJzY29wZXMiOlsicmVzdCIsImF1dGhlbnRpY2F0ZWQiXX0.PMsoUCc48R93lvg7lI3k1Cm2JuGmMjMmxv8HpWfKcEUVSxJYAvHaBv7DlI4EU_CSRQvi7qZBa0TvsdN0kLvp4Fl-T2aUXvAunyxKifTk76VlTuKT34QYcrdGHTLkjnmmCqeski8a8AeAWjDCC0q25_WgLQtSZD9fUHN-brjKl0qttm4r40ItvCif1tN2yg5J8WdTge8hzs46nM4s0_06HKrhTKiVo-YH0BT-3fR49A_l8j9jHdS8m8bCjvWcvOArgHAFVDl-6zXDk-hC43w07b_rUaKEwRDxkJeYVkdn3RZiF1BWEVG9A72gbdhprhKb6W9LxiE8C-1RtiZ0yY4F_Q',
'Content-Type': 'application/hal+json',
'Accept': 'application/hal+json'
}

Uploading an image.

The last thing that I want to show you is image upload. In order to enable this functionality, we had to install the File Entity drupal module and enable some permissions that it comes with, both in the Drupal Permissions tab and in the REST UI interface. After that, we would be able to create a POST request in order to upload an image to our Drupal instance. The path that we are going to be posting to is /entity/file?_format=hal_json and the data structure you could check beneath, to which of course should be added the Authorization header. Something interesting is that as we are sending the POST request we should add a data value the base 64 encoded version of the image. If you want to test with postman you could just find an online base 64 image encoder and for example, if you are doing it with Python, you would be doing it in the code that is preparing and making the request to the Drupal API.

url: http://{your_drupal_url}/entity/file?_format=hal_json
type: POST
data: {
"_links":
{"type"{"href":"{your_drupal_url}/rest/type/file/image"}},
"filename":[{"value":"peas.jpg"}],
"filemime":[{"value":"image/jpeg"}],
"type": [{"target_id": "image"}],
"data":[{"value": "BASE64_VERSION_OF_THE_IMAGE_HERE"
}]
}
headers:
{
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjIzYTZhMjMwODYxZGUwYjZkOWExM2I4ZjQ2ZTdmOTIzZjRiNjgwMjU4MjNkNWY3OTYwYmRiMDY0MzEzNmE1YjE5NTEyMTc0OTM0ZWZmOTdlIn0.eyJhdWQiOiJiMWNhZjdjOC0zODI0LTRmYWEtYjNjNi05ODU4ZmVjZTFlMjAiLCJqdGkiOiIyM2E2YTIzMDg2MWRlMGI2ZDlhMTNiOGY0NmU3ZjkyM2Y0YjY4MDI1ODIzZDVmNzk2MGJkYjA2NDMxMzZhNWIxOTUxMjE3NDkzNGVmZjk3ZSIsImlhdCI6MTU1MTA5MTIxMywibmJmIjoxNTUxMDkxMjEzLCJleHAiOjE1NTExOTEyMTEsInN1YiI6IjIiLCJzY29wZXMiOlsicmVzdCIsImF1dGhlbnRpY2F0ZWQiXX0.PMsoUCc48R93lvg7lI3k1Cm2JuGmMjMmxv8HpWfKcEUVSxJYAvHaBv7DlI4EU_CSRQvi7qZBa0TvsdN0kLvp4Fl-T2aUXvAunyxKifTk76VlTuKT34QYcrdGHTLkjnmmCqeski8a8AeAWjDCC0q25_WgLQtSZD9fUHN-brjKl0qttm4r40ItvCif1tN2yg5J8WdTge8hzs46nM4s0_06HKrhTKiVo-YH0BT-3fR49A_l8j9jHdS8m8bCjvWcvOArgHAFVDl-6zXDk-hC43w07b_rUaKEwRDxkJeYVkdn3RZiF1BWEVG9A72gbdhprhKb6W9LxiE8C-1RtiZ0yY4F_Q',
}
The image file, created with our POST request.

Conclusion

In this article, we covered most of the main things that we have to know about starting with the Drupal 8 REST API. The Drupal instance from this example, packaged with docker to make it easy to install you could find on GitHub here.

Thanks for reading and if you liked and you found this article useful, please give it a clap.

Resources

The Simple OAuth module on drupal.org
The REST UI module on drupal.org
The File Entity module on drupal.org
The Drupal + Docker repo of the example from this article