thomasaull

Rest-API site profile

Recommended Posts

I recently started to build Vue SPAs with ProcessWire as the backend, connected with a REST API. Thanks to code and the help of @LostKobrakai (How to use FastRoute with ProcessWire) and @clsource (REST-Helper) I got it up and running pretty quickly and now have put all of it in a site profile for others to use. It includes the REST API with routing for different endpoints, JWT Auth and a simple Vue SPA which shows the process of logging in a user (nevertheless, you don't have to use the Vue part, the API will work on it's own).

Check it out here: https://github.com/thomasaull/RestApiProfile

I'm pretty sure, it's not the perfect or most sophisticsted solution, but it gets the job done for me…

Feedback or Improvements are very welcome :)

  • Like 25
  • Thanks 2

Share this post


Link to post
Share on other sites

Seems great. Awesome Job!.

Thanks for sharing.

I think the JWT Auth may become a module on it´s own.

For folks who don´t know about JWT. It´s a standard for Authentication Using Json.

https://jwt.io/

:)

  • Like 2

Share this post


Link to post
Share on other sites
4 hours ago, thomasaull said:

I recently started to build Vue SPAs with ProcessWire as the backend, connected with a REST API.

Sounds awesome, never done anything like this before, can we see an example application?

Share this post


Link to post
Share on other sites
3 hours ago, SamC said:

Sounds awesome, never done anything like this before, can we see an example application?

Unfortunately, one is not public – the other one not finished (not public either, but I might put in in my portfolio with a screencast or anything)

  • Like 2

Share this post


Link to post
Share on other sites
5 hours ago, clsource said:

I think the JWT Auth may become a module on it´s own.

Are you working on it? ;) I'm not sure if you need a module for that, the only thing which makes sense being managed by the backend is the JWT Secret maybe… But by all means it might make things easy for people who are not familiar with JWT!

Share this post


Link to post
Share on other sites
13 minutes ago, thomasaull said:

Unfortunately, one is not public – the other one not finished (not public either, but I might put in in my portfolio with a screencast or anything)

Yeah that'd be good, thanks.

Share this post


Link to post
Share on other sites

Nice! 

But I wonder if the JWT are really necessary here—or why the implementation?
Doesn't the server session / processwire handle that already?

Share this post


Link to post
Share on other sites
On 4/12/2017 at 6:19 PM, thomasaull said:

Are you working on it? ;) I'm not sure if you need a module for that, the only thing which makes sense being managed by the backend is the JWT Secret maybe… But by all means it might make things easy for people who are not familiar with JWT!

Not really at the moment. Just an idea :)

4 hours ago, blynx said:

Nice! 

But I wonder if the JWT are really necessary here—or why the implementation?
Doesn't the server session / processwire handle that already?

JWT is just a communication system.
If you look at the profile is the Processwire auth system that is used at the end.

Instead of using oAuth or Basic Authentication
JWT can be used for getting user roles and data without requiring to log in the user on every request,
saving server resources :)

 

  • Like 2

Share this post


Link to post
Share on other sites
15 hours ago, blynx said:

Nice! 

But I wonder if the JWT are really necessary here—or why the implementation?
Doesn't the server session / processwire handle that already?

It's not necessary, just one way of authentication. Basic Auth is perfectly fine aswell :)

  • Like 1

Share this post


Link to post
Share on other sites

On a sidenote jwt is perfect when things need to "scale" (i.e. in a microservice architecture). Just share the secret across multiple instances and you don't have to care about syncing sessions and related data. The backend thus can be "stateless" (regarding logins) and just used to deliver data.

 

  • Like 3

Share this post


Link to post
Share on other sites

@thomasaull Thanks for the profile. Can see you've put a lot of time and effort into it.

Have been reading up on PWA's and while Vue.js seems to be the flavour of the month, I really don't like its '/#/' in the URL. Seems Stencil.js is a very similar option and gets around the URL issue. I'm new at this and decided to try your Vue.js option first.

Got through most of it thanks to your docs and then got stumped big time at the login process.

All good to here using postman:

5a83f33c75642_Screenshot2018-02-1419_28_30.png.d5d92b206e3913e06c536cf825dbc5e3.png

From then on, I got completely lost:

5a83f3a3606e0_Screenshot2018-02-1419_30_13.png.c24ffde9e363da253bf0f69f5233548d.png

Did all that then used postman to try to get the JWT-token and log in. Nope... every response was "user is not logged in"

After many & varied attempts, I finally realised that the first conditional in /site/templates/api/Auth.php says if the user isn't log in, throw a WireException... OK but I'm not logged in. I'm trying to log in...

Commented out the WireException then my postman GET request gave me my JWT token.

Tried to login from my site but still no access???

 

Doesn't seem to matter whether I have $authActive set to true or false.

What am I doing wrong?

At what point, if ever, does $authActive get set back to true?

Any and all help, especially if they involve more detailed help docs (examples are great!!!!), much appreciated.

Thanks

psy

 

 

Share this post


Link to post
Share on other sites

Got a bit further... this time the error reporting in Router.php pointed to Tracy Debugger which is not included in the profile:

  public static function handle($class, $method, $vars)
  {
//      $authActive = true;
      $authActive = false;
//     if(!$authActive) \TD::fireLog('WARNING: Auth disabled!');

Another thing I did not know how to fix was that the Vue build files only worked when uploaded to the same domain as PW. When I uploaded the final build static folder and index.html to another domain, it failed with:

Screenshot-2018-02-15-08_32_03.jpg.ddc7c69e2e2de27741a43ce7eef5f4f7.jpg

 

Does this mean I can only have one Vue SPA per PW instance? Would be good to have the ability for one PW instance to serve multiple Vue sites.

What's your recommendation for configuring, eg install PW in a subfolder and have the Vue build files in the root?

TIA

psy

 

Share this post


Link to post
Share on other sites

Still moving along slowly

Auth.php line 37 changed:

  public static function login($data) {
//      ApiHelper::checkRequiredParameters($data, ['username', 'password']);
      ApiHelper::checkAndSanitizeRequiredParameters($data, ['username', 'password']);

Next... in ApiHelper.php - again Tracy Debugger not installed/included in default profile:

      // Sanitize Data
      if (!$sanitizer) {
//        \TD::fireLog('WARNING: No Sanitizer specified for: ' . $name . ' Applying default sanitizer: text');
        $data->$name = wire('sanitizer')->text($data->$name);
      }

That then forced the next bit in ApiHelper.php to fail too, ie:

     $data->$name = wire('sanitizer')->$sanitizer($data->$name);

as $sanitizer is empty.

Maybe not me after all.

@thomasaull suspect you have the client file versions out of synch in the profile on github... what with TD calls and incorrect function names, etc. Please confirm.

Share this post


Link to post
Share on other sites

Sorry for the hazzle @psy. I‘m on the road atm, how important is this? I can try to get you some assistance maybe later today

Share this post


Link to post
Share on other sites

Hi @thomasaull keen to get it resolved but not urgent in the next few hours. When you get it right is best :) 

Just grateful for the API and want to get it working. Have lots of applications for a fully functional PW REST API that handles logins and sanitizer.

Happy to help you quality control/troubleshoot. Maybe we can take this conversation offline until the profile is ready?

Keep me posted.

Thx

 

Share this post


Link to post
Share on other sites

Ok so apparently my flight got wifi, so I'm able to answer now :)

Vue Router is also capable of suppressing the /#/ in URLs, you just need to add the HTML5 History Mode: https://router.vuejs.org/en/essentials/history-mode.html

Did you actually check and run the Vue Login example? Is everything working as supposed there?

The login does happen in the login function in Auth.php, the other function just makes sure the user is actually logged in and therefore allowed to query the API. 

$authActive never gets changed after the initial declaration, it's basically just a switch to make the API publicy available or not. 

To get rid of the errors by TracyDebugger you can just delete these lines, they are just in there for debugging. 

You can totally have multiple Vue SPAs querying the same API, although I have never tested this with this profile. There might be CORS related issues that needs to be solved first. For starters, I'd try to have it all under the same domain.

The checkAndSanitize works with. name|sanitizer pairs, check this line: https://github.com/thomasaull/RestApiProfile/blob/master/templates/api/Test.php#L17 (message is the parameter you're sending, text is the sanitizer which should be applied)

I think I forgot to change the function call in Auth.php, try: ApiHelper::checkAndSanitizeRequiredParameters($data, ['username|text', 'password|text']);

Try to change this line, remove all TracyDebugger statements and run the Vue example. I guess it should work then! I'm not with my computer until end of next week, so not really a big help I'm afraid 🙈

Share this post


Link to post
Share on other sites

Hi @thomasaull

In answer to your questions:

- Vue Router - thanks for the tip. Hadn't got that far yet

- Have run the installed-by-profile Vue Login example countless times - that's where I'm stuck. On the times I do get logged in, I'm only ever logged in as 'guest'

- OK I think about $authActive - but in the destructions you ask the dev to to change $authActive to false

- Yep, figured that but then in ApiHelper.php, $sanitizer is null so everything falls in a heap

- OK

- Ahhh yes, the line is just username, password - not username|text, password|text

Will try tomorrow. It's late here. Thanks for the help and safe travels

Cheers, psy

 

Share this post


Link to post
Share on other sites

Yay!!! Got it working successfully with a few tweaks to @thomasaull's templates.

In Auth.php:

  public static function login($data) {
//      ApiHelper::checkRequiredParameters($data, ['username', 'password']);

//    $data->username = wire('sanitizer')->selectorValue($data->username);
//    $data->password = wire('sanitizer')->string($data->password);

// All the below work as expected including logging an incorrect Sanitizer method
//    $data =   ApiHelper::checkAndSanitizeRequiredParameters($data, ['username', 'password']);
//    $data =   ApiHelper::checkAndSanitizeRequiredParameters($data, ['username|selectorValuexxx', 'password|string']);
      $data =   ApiHelper::checkAndSanitizeRequiredParameters($data, ['username|selectorValue', 'password|string']);

    $user = wire('users')->get($data->username);
...

The changes to ApiHelper::checkAndSanitizeRequiredParameters($data, $params) were a bit more intense:

  public static function checkAndSanitizeRequiredParameters($data, $params) {
    foreach ($params as $param) {
      // Split param: Format is name|sanitizer
      $name = explode('|', $param)[0];

        // Check if Param exists
        if (!isset($data->$name)) throw new \Exception('Required parameter "' . $param .'" missing!', 400);

        $sanitizer = stripos($param,'|') !== false ? explode('|', $param)[1] : 'text';

        // Check the sanitizer type is valid (Array copied from Sanitizer method testAll)
        $sanitizers = array(
            'alpha',
            'alphanumeric',
            'array',
            'bool',
            'date',
            'digits',
            'email',
            'emailHeader',
            'entities',
            'entities1',
            'entitiesMarkdown',
            'fieldName',
            'filename',
            'float',
            'int',
            'intArray',
            'intSigned',
            'intUnsigned',
            'markupToLine',
            'markupToText',
            'minArray',
            'name',
            'names',
            'pageName',
            'pageNameTranslate',
            'pageNameUTF8',
            'pagePathName',
            'pagePathNameUTF8',
            'path',
            'purify',
            'removeNewlines',
            'selectorField',
            'selectorValue',
            'string',
            'templateName',
            'text',
            'textarea',
            'unentities',
            'url',
            'varName',
        );
        if (!in_array($sanitizer, $sanitizers))
            throw new WireException(_("Invalid sanitizer type: $sanitizer for param $name"));

      $data->$name = wire('sanitizer')->$sanitizer($data->$name);
    }
    return $data;
  }

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By modifiedcontent
      I have one central website, with membership registration and content etc., and then several related websites with their own URL/domains, each on Processwire, all on the same server.
      I would like to access the database of the central website from the sister websites. How would I do that?
      You can't bootstrap one PW installation into another. You can include template parts from one in the other by just using the server path, but whatever you try to get/post just comes/goes to the database of the site you are on.
      Could you switch databases by including the config.php from another PW installation somewhere?
      What is the correct, secure way to do this?
    • By celfred
      Hello,
      I have a feeling I'm missing an easy thing here (again...). If I trash a page from API with :
      mypage->trash()
      Is there a simple way to restore it if needed ? I can do it in back-end, but I'd like to do it through API with something like
      mypage->restore()
       but this doesn't exit  
      So if you can give me a little help on that, I'd appreciate. Thanks in advance !
    • By jrtderonde
      Hey,
      I'm working around a module that adds pages in my ProcessWire installation. The pages are added based on a JSON array that will be imported through a page save. Everything works fine except for when the pages are added. I get the following error
      Integrity constraint violation: 1062 Duplicate entry '3e215ecd6774fd99c2b0eb5cadf36a07-1269' for key 'name_parent_id' I'm using the following code/loop to generate the pages.
      // Loop through the files foreach ($p->importFile as $file) { // Set the file location $name = $file->data["basename"]; $path = $p->importFile->path; $location = $path . $name; // Get the file $json = file_get_contents($location); $json = json_decode($json); // Loop through the rows of the import foreach ($json->ttEntityDelAddrLink as $client) { // Create new page $new = new Page(); // Create unique hash $unique = md5(date("Y-m-d H:i:s") . "-" . $client->CustomerCode); // Set some variables for the new page $new->setOutputFormatting(false); $new->template = "_client"; $new->parent = $p; // Create hash $new->title = $client->DelAddressName; $new->name = $unique; // Page specific fields $new->company = $client->DelAddressName; $new->companyId = $client->CustomerCode; $new->city = $client->DelAddressCity; $new->address = $client->DelAddressStreet; $new->postcode = $client->DelAddressZipCode; $new->country = $countries[$client->DelAddressCountryCode]; // Save the page $new->save(); } // Exit for debugging exit; }
      Does anybody know what's wrong?
    • By prestoav
      Hi folks,
      I have a marketplace site built on PW which I'd like to tie to a subscription payment system for those selling. The user accounts currently use specific PW user accounts. I'm looking closely at PayWhirl to manage payments and subscriptions as it looks ideal for subscriptions. Their API seems to work well too from initial testing.
      I wondered if anyone else in this super forum had used PayWhirl and had any advice or tips & tricks?
      Thanks.
      https://app.paywhirl.com/
    • By Harmen
      Hello all,
      Was wondering if any of you tried to include the Exact PHP client by Picqer in Processwire (URL)? I am trying to at the moment but I am stuck at the Authorization process. Building it inside a module file to execute a certain function every 6 hours to update something on the website. For the authorization process the file has to execute the following piece of code:
      header('Location:'. $urlThatRedirectsMeToTheLoginOfExact); But each time I am trying  to do so, I get the following error: Cannot modify header information - headers already sent by (output started at /processwire/index.php:55). I am out of options so that's why I am trying to find someone who used this library and could get it to work or someone who knows how I can fix it.
      Tips are highly appreciated (if they work ;))
      Cheers, Harmen