Build API endpoint in Twig with basic Authentication

Hi Folks,

According to the documentation since OctoberCMS v3 it is possible to build API endpoints using Twig.
I read that it is also possible to use layouts as middleware in this case and add condition to API logic.

As documentation says:

description = "API Authentication"
is_priority = 1
==
{% if someCondition %}
    {% page %}
{% else %}
    {% do response({ message: 'Condition not met' }, 400) %}
{% endif %}

Could you please help me how would it be possible to add condition that checks bearer token of the request, or how to connect it with RainLab.Users authentication?

For example if I have an API endpoint but I would like to protect it (make it non-public), what is the best practice to do it?

Thanks in advance for your help,
vimre

1 Like

So you could generate API keys using tailor and some event logic. A user can create a API key and it’ll be stored in a Tailor Entry definition.

You can then setup a middleware on that page in the PHP section and check if the URL has an API in the query, then run the code.

That’s how I would do it.

Thanks @artistro08
You gave me some very good ideas.

I almost did it as you wrote, but instead of sending the API key in the URL I passed it in bearer token. I found out that it is possible to get bearer tokens from the request with bearerToken() method.

I do not know how secure it is but it will do it for now. :slight_smile:

1 Like

We’ve improved the user plugin to include built in JWT support. This advice will work when RainLab.User v2.1 is released along with October CMS v3.3.11 and above.

Here’s what is included in the readme. Hopefully it helps with this requirement:


Working with APIs

When building API endpoints using CMS pages it can be useful to use a page for handling the authentication logic. The following is a simple example that includes various API endpoints.

title = "User API Page"
url = "/api/user/:action"

[resetPassword]
[account]
[session]
verifyToken = 1
==
{% if this.param.action == 'signin' %}
    {% do response(
        ajaxHandler('onSignin').withVars({
            token: session.token()
        })
    ) %}
{% endif %}

{% if this.param.action == 'register' %}
    {% do response(ajaxHandler('onRegister')) %}
{% endif %}

{% if this.param.action == 'logout' %}
    {% do response(ajaxHandler('onLogout')) %}
{% endif %}

{% if this.param.action == 'refresh' %}
    {% do response({ data: {
        token: session.token()
    }}) %}
{% endif %}

An API layout to verify the user can be used for other API endpoints.

description = "Auth API Layout"
is_priority = 1

[session]
verifyToken = 1
==
{% if session.user %}
    {% page %}
{% else %}
    {% do abort(403, 'Access Denied') %}
{% endif %}

If using Apache, you also need to make sure these lines are included in the .htaccess file.

##
## Handle Authorization Header
##
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

Thank you very much @daft
I have made a few quick test and it works perfectly.
I am not yet familiar with JWT authentication but I was able to call the sample request via Postman.

I also tried to call request with Http client feature of Laravel as well but it did not work for some reason.

Http::post('http://vimre.test/api/user/signin', [
    'login' => 'user1@vimre.hu',
    'password' => 'Password123!'
]);

Response contained a token but returned user value was null, while when I used Postman then it returned the whole User model properly.

Could you please help me how should I call API with Laravel Http client along with your JWT implementation?

Thanks in advance!

When the user is null, it means there is no session available (Laravel Http client is not a browser and does not store cookies). The token replaces the session so this is safe to ignore.

Let’s assume we have an action called “me”:

url = "/user/api/:action"

[session]
verifyToken = 1
==
{% if this.param.action == 'me' %}
    {% set user = session.user %}
    {% if not user %}
        {% do abort(403, 'Access Denied') %}
    {% endif %}
    {% do response({ data: {
        id: user.id,
        email: user.email,
        first_name: user.name,
        last_name: user.surname
    }}) %}
{% endif %}

Using Laravel, the request looks like this:

$jwtToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9...";

$request = \Http::withHeaders(['Authorization' => "Bearer {$jwtToken}"])
    ->post('http://vimre.test/api/user/me');

$data = $request->json();

Thank you for the explanation. It is clear now.
So every time I would like to call API “me” action first I have to call “signin” action to get the actual token then use this token in other API calls as bearer token.

Just one short remark:

For some reason the sample that you wrote returned empty string to me.
Instead of this:
Http::post('http://vimre.test/api/user/me')->header('Authorization', "Bearer {$jwtToken}");

It works when I use this syntax:
Http::withToken($jwt_token)->post('http://vimre.test/api/user/me');

Thanks! I updated my example.

The Http::withToken call is much better!