Creating JWKS.json file in PHP – Rob Allen

In order to verify a JWT created with an asymmetric key, the verifier needs to get the correct public key. One way to do is described in RFC7517 which describes the JSON Web Key format.

Within the header of the JWT there is a kid property which is the key ID which is then used to find the correct key within a list provided at the /.well-known/jwks.json endpoint.

The JWT header therefore looks something like this:

{ "alg" : "RS256", "kid" : "6eaf334518784ff392c3123b41ae49f5", "typ" : "JWT"
}

And the jwks.json is structured something like this:

{ "keys": [ { "alg": "RS256", "kty": "RSA", "use": "sig", "kid": "6eaf334518784ff392c3123b41ae49f5", "n": "sj6R1AYPKISqYKFxmQMMFJSm583Jfn6ef51SpQPCe17SM10Ljp2YIte924U ...", "e": "AQAB" } ]
}

This is an interesting format as it doesn’t use the standard PEM format for the key, but rather stores it as a modulus (“n”) and exponent (“e”) as per RFC 7518 section 6.3:

6.3.1.1. “n” (Modulus) Parameter
The “n” (modulus) parameter contains the modulus value for the RSA
public key. It is represented as a Base64urlUInt-encoded value.
Note that implementers have found that some cryptographic libraries
prefix an extra zero-valued octet to the modulus representations they
return, for instance, returning 257 octets for a 2048-bit key, rather
than 256. Implementations using such libraries will need to take
care to omit the extra octet from the base64url-encoded
representation.
6.3.1.2. “e” (Exponent) Parameter
The “e” (exponent) parameter contains the exponent value for the RSA
public key. It is represented as a Base64urlUInt-encoded value.

Fortunately, we can use openssl to sort this all out for us:

// $keyString is a PEM encoded public key
$key = openssl_get_publickey($keyString);
$details = openssl_pkey_get_details($key);

Assuming $key is an instance of OpenSSLAsymmetricKey and $details is an array, then:

$modulus = $details['rsa']['n'];
$exponent = $details['rsa']['e'];

Putting this into a PSR-15 request handler that is passed an array of public keys, we can put together a jwks.json response:

<?php declare(strict_types=1); namespace App\Handler; use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Webmozart\Assert\Assert; class JwksHandler implements RequestHandlerInterface
{ /** * @param string[] $publicKeys */ public function __construct(private readonly array $publicKeys) { } public function handle(ServerRequestInterface $request): ResponseInterface { $keys = []; foreach ($this->publicKeys as $keyString) { $key = openssl_get_publickey($keyString); Assert::isInstanceOf(\OpenSSLAsymmetricKey::class, 'Public key is not valid.'); $details = openssl_pkey_get_details($key); Assert::isArray($details, 'Public key details are not valid.'); $keys[] = [ 'kty' => 'RSA', 'alg' => 'RS256', 'use' => 'sig', 'kid' => sha1($keyString), 'n' => strtr(rtrim(base64_encode($details['rsa']['n']), '='), '+/', '-_'), 'e' => strtr(rtrim(base64_encode($details['rsa']['e']), '='), '+/', '-_'), ]; } return new JsonResponse(['keys' => $keys]); }
}

Note that we remove the base64 padding (= at the end) and also use the Base64Url modification where “+” is replaced with “-” and “/” with “_“.

The other properties in the JSON object are:

  • kty: Key Type – the cryptographic algorithm family used with the key
  • alg: Algorithm – the specific cryptographic algorithm used.
  • use: Use – the intended use of the public key. “sig” for signature, “enc” for encryption.
  • kid: Key ID – used to match a specific key

The verifier reads the jwks.json file and iterates over the list to find the one that matches the kid in the JWT header that they are trying to verify. When they find it, the can then convert the modulus exponent back into a public key and verify the JWT as per usual.

A quick guide to JWTs in PHP – Rob Allen

The most common use of JWTs is as an authentication token, usually within an OAuth2 workflow. Creating these tokens is part and parcel of the authentication library that you use.

I recently had a requirement to use a JWT independent of authentication and these are some notes on what I learned when researching with Lcobucci\JWT.

Make up of a JWT

To really understand JWTs, read RFC7519. For a more readable introduction, read the one on jwt.io.

Also, JWT is pronounced “jot“.

The most important thing about a JWT is that it contains data and a signature. The signature allows you to verify that the JWT’s data hasn’t been tampered with. This works because the signature is signed with a secret. This can be a shared secret (a symmetrical algorithm) or a public/private key pair (asymmetric algorithm).

In my case, I’m only interested in signing a JWT using a public/private key as my client cannot securely hold a shared secret.

Creating a public/private key

OpenSSL is your friend here. Create a keys directory and then use the command line.

To create a private key:

openssl genpkey -algorithm RSA -out keys/private.key -pkeyopt rsa_keygen_bits:2048

To create the public key from the private key:

openssl rsa -pubout -in keys/private.key -out keys/public.key

You will now have two files in the keys directory: private.key and public.key. Keep private.key safe and make public.key available to your clients.

Creating a token

Using Lcobucci\JWT, we create a Configuration:

use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory; $configuration = Configuration::forAsymmetricSigner( new Sha256(), InMemory::file(__DIR__ . '/keys/private.key'), InMemory::file(__DIR__ . '/keys/public.key')
);

From our configuration, we can obtain a builder and specify our token:

$keyId = '40597EB1-5E20-49B5-BDDF-B24D1B3B05B5';
$subject = '851828E8-C376-4026-9FD8-D2CCE406CD1C';
$now = new \DateTimeImmutable(); $builder = $configuration->builder() ->identifiedBy($keyId) ->relatedTo($subject) ->issuedBy('https://app.example.com') ->issuedAt($now) ->expiresAt($now->modify('+2 weeks')) ->withClaim('foo', 'bar');

The data elements in a JWT are called claims. There are a number of registered claims that are not mandatory, but you should set if they are relevant as all clients will recognise them and their purpose. In particular JWT ID, issuer, subject, issued at and expiration time are particularly useful. There are also a set of public claims that provide a standardised set of key names for common information which help avoid clashes.

Then there is the data specific to your application which are known as private claims. These can be whatever you like and in the example above, we have created a claim called “foo”.

Finally we create the token itself:

$token = $builder->getToken($configuration->signer(), $configuration->signingKey());
$tokenString = $token->toString();

We can then send the token string in response to an API request, etc.

Validating a token

When we receive a token, we need to validate it. This means that we check that it hasn’t been tampered with and we can also check that the data within it is as expected.

Firstly, we parse the token string back into a token object:

use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token\Parser; $parser = new Parser(new JoseEncoder());
$token = $parser->parse($tokenString);

To validate it, we need a validator and a set of constraints that the validator will test the token for:

$validator = new Validator(); $clock = new SystemClock();
$constraints = [ new SignedWith(new Sha256(), InMemory::file(__DIR__ . '/keys/public.key')), new LooseValidAt($clock), new IssuedBy('https://app.example.com'),
];

The three constraints here check that the JWT:

  • has been signed with the private key (by using the public key to verify)
  • is current and has not expired
  • was issued by the expected issuer

There are other constraints too – check the docs.

Note that the time based constraints use the PSR-20:Clock interface, so we use the original (another 719 bytes)

python-oracledb 2.1 and node-oracledb 6.4 have been released – Christopher Jones

I’m still on a long sabbatical so this will be a brief post. In my absence our Oracle Database driver team has been busy and are proud to announce that python-oracledb 2.1 and node-oracledb 6.4 have been released. Also our C Oracle Database Programming Interface for Drivers and Applications ODPI-C 5.2 is available from GitHub.

Photo by Jingda Chen on Unsplash

You can read about node-oracledb 6.4 for Node.js in Sharad Chandran’s post Node-oracledb 6.4 offers improved LOB and OSON support.

ODPI-C 5.2 release notes are here.

To see what’s new in the python-oracledb 2.1 release for Python, review the release notes and check out the considerable number of enhancements and fixes. Some highlights are:

  • asyncio support is out of pre-release status.
  • Some of the great behind-the-scenes work to reduce connection establishment timea for Oracle Database can now be used in the python-oracledb’s default Thin mode. (Thick mode will automatically get these benefits when compatible Instant Clients or Oracle Client libraries are used). In addition to the benefits for all Oracle Database 23c users, there is a new, extra option to use “TCP fast open” support when connecting to Oracle Autonomous Database Serverless (ADB-S).
  • Ongoing work exposes more of Oracle Database’s advanced JSON support, in particular to let you take advantage of Oracle’s efficient internal storage format (“OSON”) with its extended data types.

Installing or Upgrading python-oracledb

You can install or upgrade python-oracledb by running:

python -m pip install oracledb --upgrade

The pip options–proxy and –user may be useful in some environments. See python-oracledb Installation for details.

python-oracledb References

Home page: oracle.github.io/python-oracledb/index.html

Installation instructions: python-oracledb.readthedocs.io/en/latest/installation.html

Documentation: python-oracledb.readthedocs.io/en/latest/index.html

Release Notes: python-oracledb.readthedocs.io/en/latest/release_notes.html

Discussions: github.com/oracle/python-oracledb/discussions

Issues: github.com/oracle/python-oracledb/issues

Source Code Repository: github.com/oracle/python-oracledb

Disc Golf Discs similar to Remix Discs on Amazon – Brian Moon

Remix Creature

Remix Disc Golf is a brand of disc golf discs that I have only been able to find on Amazon. The seller on Amazon is named Disc Golf Goods. On its Amazon store page, they sell MVP, Axiom, Remix and other brands of disc golf equipment. The detailed seller information on Amazon says the “Business Name” is MVP Pro Shop, LLC. It is pretty common knowledge that these discs are manufacturered and sold by MVP. The speculation is that they are molds made for other companies (Mint, Thought Space Athletics, and possibly others) which they are selling under the Remix name on Amazon. Many of the reviews mention the discs have cosmetic defects or look like they have been used. That has led some to think these are factory seconds. The cool thing is, they cost less than any of the MVP brands or third party brands for which they are known to manufacturer discs. The discs sell from $9.95 to $12.95.

One thing people are always trying to figure out is what disc from another brand was renamed for a Remix disc. Well, it’s not an exact science. Some of them could be rejected molds. So, while they may be very similar to another disc, it could be a mold that was meant for another disc that was not used for that disc. This is pure speculation based on talking to people in the know for almost 28 years of playing disc golf.

Now, there is a site that already has a feature that lets one search for similar discs. It is called Try Discs. Their recommendation engine seems to favor flight number similarities over measurements. And we all know that flight numbers are kind of made up. I decided to use the PDGA specs for approved discs to find the discs most similar to the Remix discs available on Amazon. I did not limit the search to brands that are known or believed to be manufactured by MVP. Perhaps you have a favorite disc from another brand that is similar to a Remix disc. There are more Remix discs approved by the PDGA than are on thist list. However, they are not for sale anywhere I can find. I am not claiming that any of the discs will fly like one another. I am solely comparing the measuerments has observed by the PDGA.

If you are interested in some reviews of Remix Discs, Pete Collins has some on his YouTube channel.

All values are centimeters except rim configuration. To determine similarity, diameter and inside rim diameter must to be +/- 0.5cm, height, rim depth, and rim thickness must be +/- 0.2cm (it was 0.1cm in an earlier version of the blog post), and rim configuration must be +/- 1.

For details on these specifications, see the PDGA Technical Standards document.

Brand Name Diameter Height Rim Depth Inside Rim Diameter Rim Thickness Rim Configuration
Remix Disc Golf Battleship
5 / 4.5 / 0 / 2.5
21.4 1.8 1.4 18.5 1.4 50.5
Clash Discs Cherry
5 / 5 / -1 / 1
21.4 1.7 1.4 18.5 1.4 51
Clash Discs Berry
5 / 5 / -1 / 1
21.4 1.7 1.4 18.8 1.3 50.5
Kastaplast Järn
5 / 3 / 0&nbs

Truncated by Planet PHP, read more at the original (another 56738 bytes)

How I got Disc Golf Network Pro for FREE for 2024 – Brian Moon

Do you plan to go to a DGPT event this year? Are you a PDGA member? Then it could be worth it to buy the Disc Golf Network yearly plan.

Disc Golf Network (aka DGN) (the media arm of the Disc Golf Pro Tour) (aka DGPT), announced their new pricing tiers for 2024 earlier this month. It was met with some mixed reviews. Some users of the service had issues using it the first week. Most of those appear to be due to users needing to update the app on their devices or using older streaming devices that do not support the new 60fps stream. They have updated their upgrade guide. I experienced this on one of my Roku devices. I was not surprised to be honest. Many of the Roku apps we use on that device are laggy and crash from time to time. It is over 10 years old. The fact that it has kept working at all is a credit to Roku.

As for the pricing for DGN, there are three tiers: Basic, Standard, and Pro. See the link above for the differences. The pricing ranges from $5.99/mo to $19.99/mo for non-PDGA members. While PDGA members can get Basic for free, Standard for $5.99/mo, and Pro for $12.99/mo. There are also yearly options. Basic for $59.99, Standard for $129.99, and Pro for $239.99 for non-PDGA members. And for PDGA members, Standard for $69.99 and Pro for $139.99. Since Basic is free, there is no yearly option for PDGA members of course. Most people I know that want to consume live disc professional disc golf are PDGA members. While some say you have to factor in the $50 annual PDGA membership cost along with the discounted DGN price, that does not apply to me. I would be renewing my PDGA membership either way. So, I will only be speaking about how and why I chose the option I did based on the discounted PDGA pricing.

The first question I had to ask is what do I want to pay monthly or go ahead and pay for the whole year? The Standard plan annual cost only saves you $2 for the year. Not a compeling reason to do it in my opinion. The annual cost for Pro actually saves more than the cost of a month, $139.99 one time compared to $12.99/mo over 12 months totaling $155.88. There are some ways to save if you change your plan for certain months for certain events or remember to cancel after the DGPT Finale in October. But, let’s be real. I won’t remember to do that. Most people won’t remember to do that. That is why the subscription model is so popular in the USA. That is how gyms stay in business to be honest. If you are the kind of person that likes to manage subscriptions that way, go for it. If you micro manage it completely and only pay for February through October and upgrade the months of the USDGC and European Open, you could get all of the coverage for as low as $88.89 for the year as a PDGA member. I think I did that math right. You are probably saying “Hey, your headline says you are getting it free for the whole year! What gives?” Yes, let me get to that.

Here is why I opted for the full year, Pro plan. It’s $139.99 for the year. The kicker for me is that any yearly plan includes two free general adminssion (aka GA) weekend passes to a Disc Golf Pro Tour event as well as 10% off any other DGPT ticket purchases. As a family, we had already booked an AirBnB for Nashville in April to go watch the Music City Open before this announcement was made. My two sons and I are going for all three days. And two other family members will be joining us for Sunday. I had planned to get the weekend VIP pass for myself. So, altogether, our tickets to the Music City Open were going to cost around $350. However, with the yearly DGN option, I get the GA passes for free. And I get a 10% discount on the other tickets. Those ended up costing me around $210 after the discount. So, my savings on tickets (tickets I had already planned to buy before I knew there was a discount available) is around $140. That is the cost of the yearly plan. If you include all of the decimals in all of the math, I technically am spending 17 cents more on the DGN subscription than I am saving on tickets. Would that make a better headline?

Ticket Quantity Regular Price DGN Discounted Price
3-Day General Admission 2 $116.88 ($58.44/ea) FREE
Sunday General Admission 2 $71 ($35.50/ea)

Truncated by Planet PHP, read more at the original (another 2788 bytes)

New edition for the Rector Book – Matthias Noback

The cover of the 2024 Edition of the Rector book

A couple of weeks ago, Tomas Votruba emailed me saying that he just realized that we hadn’t published an update of the book we wrote together since December 2021. The book I’m talking about is “Rector – The Power of Automated Refactoring”. Two years have passed since we published the current version. Of course, we’re all very busy, but no time for excuses – this is a book about keeping projects up-to-date with almost no effort… We are meant to set an example here!

In the meantime, a lot has changed. Tomas has become a very successful legacy-project-saver, with this incredibly powerful tool called Rector. The project has gained a lot of traction in the PHP development community. Tomas and the co-authors of the project keep improving the tool, making it more useful, developer-friendly, faster, and more stable every day. Recently he released version 1.0 on-stage at Laracon Europe, in Amsterdam. In important moment, which indicates that we are dealing with a mature tool.

I know Tomas as a hard worker. He’ll do everything to Get Things Done. He keeps simplifying things, even in the Git project that’s behind the book’s manuscript. Always looking for ways to prevent developer (or writer) mistakes and to automate common tasks, he ruthlessly cuts away unnecessary weight. When he reads a convoluted paragraph that works around some quirk in Rector, he fixes the issue in Rector, so the text is once more easy to understand. If people ask the same questions over and over again, he adds a helpful command to Rector’s command-line interface, so the question will disappear. In other words, he has a great idea for feedback. What kind of signal does the code give us? Is this too hard to work with? Can we simplify this? What kind of signal do we get from readers? Let’s improve!

With a 1.0 version for Rector also comes a new version of the book about Rector. This 2024 Edition provides an even better start for your static analysis & automated refactoring journey. I can testify personally: once you start, you’ll never want to go back.

Read more about the updates on Tomas’ blog: Rector Book 2024 Release with Brand new Chapter

And get the new version here: https://leanpub.com/rector-the-power-of-automated-refactoring

If you already bought a previous version, you can download the latest files for free (of course!).