JWT’s for API authentication in Magento 2

With the latest 2.4.4 release of Magento 2 comes the normal improvements including fixes and security patches, but theres normally a few new and exciting modules. For 2.4.3 this was the inclusion of Magento PageBuilder in the open-source edition, built on the solid foundation of Gene Bluefoot (Full disclosure, I work for Gene, but my opinions are my own and I do not speak for the company) which allowed merchants to easily build great looking CMS and product pages with minimal knowledge of HTML and CSS.

So digging though 2.4.4 I was interested to see a Magento_JwtUserToken module. For those of you that have no idea what a JWT is, it stands for JSON Web Token. JWT’s are (if you want a more technical explanation check out RFC 7519) in summary, a way to share a verifiable payload between 2 parties. In the simplest implementation, it is 3 json objects encoded as base-64 strings, concatenated together with full-stop characters. The middle string (the payload) has the important data. That being said. If you are just storing it as a token, you don’t actually need to decode or process it, just store it.

That being said… lets examine the payload eyJ1aWQiOjEsInV0eXBpZCI6MiwiaWF0IjoxNjUzMzQwNzA1LCJleHAiOjE2NTMzNDQzMDV9 becomes {"uid": 1,"utypid": 2,"iat": 1653340705,"exp": 1653344305}. In this there are 2 (of many) “registered claims”, which are values that are not required but suggested. In our case iad (issued at) and exp (Expiration time), which for us is the interesting one. Previously, the only way of determining if an auth token was expired was to already know what the expiary time was, or to make the request we wanted or to have logic wrapped around our API calls to check if the HTTP response code indicated that the token had expired, in which case we would need to re-authenticate and squire another token

So you may be thinking “Oh, SHIT, we use the Magento API, is this going to break our ERP integration?” Almost certainly not. In fact, if you have upgraded to 2.4.4 already, your integration may be using JWT already.

Lets take a bit of a deeper dive into how this works…

Lets start off by authenticating to the Magento API with our admin credentials:

curl --location --request POST 'https://magento.test/rest/V1/integration/admin/token' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "craig",
    "password": "hunter2"
}'

All /rest/ URLs are handled by Magento_Webapi which identifies all possible endpoints by reading the webapi.xml file from all enabled modules.
So lets track track down the endpoint responsible for handling this request. Using your IDE of choice (For me and most Magento developers thats PhpStorm) lets filter for the string /V1/integration/admin/token in files webapi.xml. Now you may have noticed there are 2 results… When Magento added Two-Factor authentication as a dependancy for the core meta-package via Magento_TwoFactorAuth this included 2FA for the API as well. For the purposes of this example I have disabled this using Mark Shust’s MarkShust_DisableTwoFactorAuth (composer installable ?) module.

NOTE: I can’t tell you what to do, but please, please, please… do not disable 2FA on production sites. If you do you are doing yourself and your client’s a massive dis-service. 2FA is a brilliantly effective measure in our toolbox to mitigate malicious actors aiming to undermine us, our customers and ecommerce stores in general. Our end users trust us with their data, the least we can do is use the free tools at out disposal to safeguard it.

Looking at vendor/magento/module-integration/etc/webapi.xml we see POST requests to /V1/integration/admin/token are handled by Magento\Integration\Api\AdminTokenServiceInterface::createAdminAccessToken(). Lets jump to the implementation in Magento\Integration\Model\AdminTokenService::createAdminAccessToken() and figure out where the token is actually created.

Right on the last line of the method we see return $this->tokenIssuer->create($context, $params); Knowing that Magento uses the Dependancy Injection to provide our code with its required dependancies lets take a look at the __construct() method.

Now… The $tokenIssuer property of this class is provided to us a little ambiguously, the type is nullable and there is code to detect a nullable type in which case the ObjectManager (? I think in this case it’s done for backwards compatibility?) is used to get us an implementation of Magento\Integration\Api\UserTokenIssuerInterface

Now comes the interesting part…

In Magento 2 we use the di.xml file to specify which class handles the implementation of an interface. So, lets do another search for the preference for Magento\Integration\Api\UserTokenIssuerInterface. We get 2 results. This is where Magento’s module preferences come into play.

We have 2 modules, and 2 implementations for UserTokenIssuerInterface:

  • <preference for="Magento\Integration\Api\UserTokenIssuerInterface" type="Magento\JwtUserToken\Model\Issuer" />
  • <preference for="Magento\Integration\Api\UserTokenIssuerInterface" type="Magento\Magento\Integration\Model\OpaqueToken\Issuer" />

They can’t both do it so who do we know which one takes priority?

As it turns out module.xml holds the answer. The sequence node tells Magento which modules should be loaded before the current module. Lets take a look at the 2 files in question:

<!-- module-integration/etc/module.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Magento_Integration" >
        <sequence>
            <module name="Magento_Store"/>
            <module name="Magento_User"/>
            <module name="Magento_Security"/>
        </sequence>
    </module>
</config>
<!-- module-jtw-user-token/etc/module.xml -->
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Magento_JwtUserToken">
        <sequence>
            <module name="Magento_Integration" />
        </sequence>
    </module>
</config>

So from this we can determine modules probably load in the following order:

  • Magento_Store
  • Magento_User
  • Magento_Security
  • Magento_integration
  • Magento_JwtUserToken

Which means when the di.xml gets merged the class Magento\JwtUserToken\Model\Issuer in Magento_JwtUserToken gets defined as the preference for Magento\Integration\Api\UserTokenIssuerInterface and overrides the one that Magento_Integration defines as the preference.

This is how we are able to ensure JWTs are used as the auth token method, with no system configuration, no plugins to change to logic.

It may seem more complex at first with all the XML merging etc, but lets think about it. How often do we change the authentication logic for our APIs? Do we really need settings to choose this if the more modern way of doing things, by default, takes priority? On top of this once that cache has been warmed, this decision making does not need to happen again, it has already been computed, lets just save it for every subsequent request.

But what if we want to keep using the old API tokens? Really simple… bin/magento module:disable Magento_JwtUserToken. When we disable the module, all the XML in the code is ignored so it does not get merged, restoring the preference for the implementation defined by Module_Integration.

I also made a YouTube video if that works better for you.

Blogging and how to develop for Magento

I’ve been working as a a Magento developer for just under 4 years now. Looking back I can think of 47 different sites I have worked on to varying degrees, from styling blog pages to full custom ERP integrations.

I don’t think I would be where I am now, with the knowledge that I have without sites like the Inchoo Blog (Always great explanations), Magento Stack Exchange (Answers with varying degrees of helpfulness) and others. I now feel that I have enough experience and understanding of Magento to create some useful tutorials and how-to guides and would love to give back to the community I learnt so much from. However, I really want to avoid the “copy my code, and you’re done” style tutorials.

When I was first starting out I found these guides could be useful… if you needed to do exactly what they were doing in the guide and nothing else, but as anyone who has ever dealt with Magento will know, it’s not always that simple. You will have business, client or process requirements to fill so all of a sudden that guide you are following isn’t quite as useful as you first thought. As you learn more and more you have the understanding to change these examples for your own needs, but when you’re staring out, or haven’t worked with that particular feature sometimes its not so obvious.

What I’m aiming to do here is to put code up, but also to actually explain what its doing and why we are doing it that way. I’m hoping this gives people the information and knowledge to be able to understand enough to do what it is they need to without being stuck looking for another guide.

I have a bunch of posts that I’m currently drafting covering a number of subjects including:

  • MSI (Multi-Source Inventory),
  • Certification,
  • Using and creating APIs,
  • Effective use of di.xml,
  • Various issues I come across on a day-to-day basis and solutions.

I currently work for an amazing Magento agency so get to work on some really interesting projects and I’m hoping that will give me more ideas as time goes on. As I was writing this post I was thinking that it would be good to aim for a post a week, having thought about it however, even this post which is just me talking about what I want to write about has taken me a long time just to think about and actually type out in a coherent way. Some of the drafts with code I’ve already spent more than an hour or 2 on and they are not even close to completion. I think I’m going to aim for 1 per month and if I can try for every other week as I get better, but we’ll see how things go. It would also be quite cool do to some videos I think as well but I hate the sound of my own voice so I think I’ll leave that on the back burner for now.

Thanks for taking the time to read. If you want to get in touch or you have any ideas for things you want me to post about then feel free to reach out on the social networks linked up top.

Brewdog for a non-beer drinker

Probably a year or two ago now I invested in Brewdog as a bond-holder. For those of you that don’t know what a bond is it is similar to a loan. I effectively “leant” £500 to Brewdog and get around 6.5% P/A ROI. Any of you that know me may think that is a strange thing for me to invest my hard earned pennies in as I really can’t stand beer. For someone with a large amount of liquid capital this would just be another investment opportunity, but for me, it was kind of a big deal.

So why? Well, today as a perfect example of why I invested in them. I had nothing to do, all my fiends were busy, so I went there for lunch/dinner on my own. Yes, I, know, a bit sad but if anyone wants to go with me I will be more than happy to do so. I have been to the Brighton bar a lot. The staff immediately recognised me, greeted me with a smile and knew what I liked to drink. In this day and age of diminishing customer service it is a fresh breath. I really appreciate this and I’m proud to have put what I have behind them, however insignificant of a difference I may have made. In particular the bars in Brighton, Camden and Soho, thank you for making me feel welcome. I don’t feel I’m alone in getting a bit peeved with companies where the staff treat you as an inconvenience rather than a source of revenue, you guys certainly stands our from the crowd!

On one occasion James Watt (Capitan of Brewdog) was behind the bar. Bit if a shocker, but incredibly pleased to see staff from high-up on the front lines instead of stuck behind a desk.

One thing is clear across all staff from all branches of this company. They are passionate, able to speak with fluency and recommend drinks. Why would this matter to me? After all, I only drink cider! On the few occasions they did not have a cider on tap/bottle they asked what I did/didn’t like and were able to recommend a drink for me. I didn’t like it as much as what I would normally be drinking but they recommended me a drink I could finish and actually kinda enjoy.

Ps: Brewdog… if you’re reading this, I know you have your hands full with Lone Wolf, US brewing facilities and more bars, but I’d bloody love it if you could crank out your own cider at  some point.

Long running tasks over SSH

Recently I found myself having to execute some long running tasks (running find with -exec on a directory with millions of files) on a linux server over SSH. This can cause a bit of an issue if you want to go away and do something else or if you have an unreliable internet connection. This is because as soon as you SSH session disconnects the current process ends. This happens wether you close the connection yourself or the frustrating packet_write_wait: Connection to some.ip port 22: Broken pipe error. There are a couple of solutions to this:

Screen

Screen is a program that allows you to have multiple “windows” open without having to create separate SSH sessions for each process. I had always meant to learn how to use this program but there were a lot of options and I always put it off, however like many programs I found there are only a few options you need to learn to get work done.

To get started first SSH into the server we need to run the task on and run screen.

In my case I got this: -bash: screen: command not found

Not a problem, just install with sudo yum install screen and select yes when asked to confirm.

installing screen

I’m on CentOS so used yum, for debian based distros use apt-get

Now it’s installed lets try again. You may be presented with a welcome screen but you can just tap space or enter to continue. This will take to to a new empty shell. From here you can run tasks a normal. Lets say you are trying to find all .txt files in your home directory you might use something like find ~/ -name *.txt and depending on how many files it has to search through this may take a while. Not to worry. Press Ctrl+a then d and your screen will be “detached” and you will be returned to the main shell:

[code]
[detached from7938.pts-2.newbury]
[craig@newbury ~]$[/code]
From here you can close your SSH connection and come back in your own time. To “re-attach” simply screen -r yourSessionId in my case this would be screen -r from7938.pts-2.newbury If you can’t remember the session ID you can also run screen -ls which will show you something like:

[code]
[craig@newbury ~]$ screen -ls
There is a screen on:
7938.pts-2.newbury (Detached)
1 Socket in /var/run/screen/S-craig.

[craig@newbury ~]$
[/code]

Next run screen -r 7938.pts-2.newbury and you will be re-attached back to the session.

Theres also a handy feature to name sessions. From within the session type Ctrl+a

then :sessionname mysessionname

 

Nohup

This is another program that allows you to run programs without the risk of them being killed on disconnect. In-fact they way I have been using this I haven’t needed to come back, leaving it run on it’s won was fine so nohup some-command & did the job fine. The & tells the OS to put the job into the background. If you need to see if its still running then jobs -l will do the trick for you. You will get output similar to this:

[code]
jobs -l
[1]+ 13100 Running nohup find / &
[/code]

Where “13100” is the PID and “nohup find / &” is the command you ran. You can still kill the process, say for example services start having latency/responsiveness issues by using kill 13100  (SIGTERM/Graceful process end) or kill -9 13100 (SIGKILL/Forcing the process to end).

 

Starting a blog

For many years I have been meaning to start a blog. Every time I have a thouht, I think “I should write this down”. I have so much going on In my head that I want to get out, and would live to get peoples thought on that I thought it was about time that I actually did something about it.

I’m a massive geek so you can probably expect to see posts about all the things I am learning including HTML, PHP, CSS, JavaScript, Magento, Swift/iOS, electronics annd much more.

Stay tuned. Hopefully you find something interesting here. Let me know what you think.