Integrating Singpass Login API with Laravel Socialite Provider — Part 2 Writing Socialite Provider

Introduce to OpenID Connect
The authentication architecture behind Singpass Login API which is OpenID connect (OIDC), which allow end to end encryption between Singpass Login API server and Relying Party server. In addition, OIDC provides a way to perform signature signing and token encryption to ensure its security and integrity.
JOSE — Javascript Object Signing and Encryption
Things to know for the latest Singpass Login API
- Client Secret is no longer required.
As you might wonder, you only get the client id for Singpass Login API, how about the client secret, and how can I authenticate with Singpass Login API without a client secret? Started from this year onwards, Singpass Login API required relying party to generate a client assertions basically a JWT digital signature that contains the Headers + Payload + Signature. As previous we have setup a JWKS endpoint, Singpass Login API server will retrieve the verification key (public key) from your JWKS endpoint and perform a signature checking against your provided client assertion. This is due to we the signature can only be produced by signing key (private key) which is stored at server side. - The OpenID Configuration Endpoint
This endpoint which contains the metadata of the open id configuration including those token url, Singpass JWKS endpoint, type of supported cryptographic algorithm supported for signature signing and token decryption. You should cache this configuration in your Laravel for at least one hour instead of keep sending request when loading the openID configuration, it will result a rate limit issues and Singpass server will temporarily blocked you out. In addition, you should always load those token url and Singpass JWKS endpoint from this configuration JSON instead of hard code it in the Laravel Socialite Provider class, this is due to the when OpenID configuration your socialite will always get the latest OpenID configuration.
https://stg-id.singpass.gov.sg/.well-known/openid-configuration - The Singpass JWKS endpoint
This endpoint which contains the Singpass signature verification key, this will be used to verify the decrypted id_token’s signature to ensure that the token payload data is not tampered by anyone. We will cover this later, of how to use this endpoint to perform signature checking against the decrypted id token.
Writing Laravel Socialite Provider
Before get started you need to install laravel/socialite into your Laravel project
composer require laravel/socialite
Create a SingpassProvider class
In your app directory create a socialite folder with SingPassProvider.php in it.
And create a class as SingPassProvider and extend the AbstractProvider class and implements the ProviderInterface contracts make sure that all the methods define in the ProviderInterface must be implemented as the code below
Writing Utility Function
Previously we have describe the used of openID configuration, and JWKS endpoint. Right now we need to write two methods which is getOpenIDConfiguration and retrieveSingPassVerificationKey, generateClientAssertions
The pseudo code can be defined as below:
getOpenIDConfiguration
- Check is there any cache named as singpassOpenIDConfig, if the cache exists then return it
- If not, send a request to openID configuration endpoint and cached it for one hourretrieveSingPassVerificationKey
- call the getOpenIDConfiguration to get the JWKS endpoint url
- Send a request to JWKS endpoint, which receive a JWKS (JSON web key sets aka an array of JWK)generateClientAssertions
- import the signing key
- create an algorithm manager which used by creating JWS
- load all the support token signing algorithm from openID config token_endpoint_auth_signing_alg_values_supported attribute
- construct the JWS using JWSBuilder with all the claims provided based on SingpassDocs
- Sign the JWT
- Create a compact serializer and serialize the signed JWT
- return the JWT
Based on above line 82 the payload data which is based on the criteria according to Singpass Login API documentation

Implement SingpassProvider methods
On line 50 above we need to swap the access_token with id_token before return the response body, this is due to Laravel Socialite will only refer access_token key as the token value.
REMARKS
Authorisation Endpoint
Singpass Login API requires RP to integrate their Singpass QR Login JS to provide QR scanning login experience via Singpass app. we will cover this on next topic on how to putting all of these together.
Implement ID Token Decryption and Encryption Methods
Once you exchange the token with the token_url_endpoint by using the auth code the provide in the callback url, you will received this JSON response included access_token, id_token, and token:
{
"access_token" : "Baumlj4B82CnZLv1GhxNoNIIWwW/xx3xp+VcJWyLbEs=", "token_type" : "Bearer",
"id_token" : "eyJraWQiOiJuZGlfc3RnXzAxIiwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJhdWQiOiJ4eE5zVGZsZVFNSG9XNnRiVWdTVk53bkxXUTB4VGVWMCIsInN1YiI6InM9Uzg4MjkzMTRCLHU9MWMwY2VlMzgtM2E4Zi00ZjhhLTgzYmMtN2EwZTRjNTlkNmE5IiwiYW1yIjpbInB3ZCIsInN3ayJdLCJpc3MiOiJodHRwczpcL1wvc3RnLWlkLnNpbmdwYXNzLmdvdi5zZyIsImV4cCI6MTYxODkyOTAxMCwiaWF0IjoxNjE4OTI4NDEwLCJub25jZSI6IkNNM1pcL1wvSytFZ1JnWnNyXC9JdHNUcDhXdTJXeEZHaDIzeUtSeFlqazJDekE9In0.TbFHH_W8Dzh8RJf6iejpZ8SJWG7ytvbxO0nUE8YgTP5tvE0JP0Tk0XmZ6t2P7FSt1BjvZ_Ids3vPMQ9lVJnX1A"
}
Based on the response above we only interested in id_token not access_token, due to the access_token is just a random key generated by SingPass, the id_token is what we need, and it is an encrypted JWE additional step such as using our decryption key to decrypt the id token and verify the decrypted token with the Singpass verification key which will be available in SingPass JWKS endpoint.
Decrypt ID Token
To decrypt ID token you need to load the decryption key (private key) into JWK via JWKFactory, please ensure that your passphrase is correct and configured in your .env if your decryption key is encrypted.
If the decryption for the id_token is success you will get a JWS, but this is not the end, we still need to verify whether this JWS is signed by SingPass authority, to do that we need to define two methods, one for fetching the JWKS from SingPass.
Once the token is decrypted and verified, mapUserToObject method will get called with a $user parameter which is a JWT, the user data will be in the JWT claims sub attribute in comma separated value.
{
"aud" : "xxNsTfleQMHoW6tbUgSVNwnLWQ0xTeV0",
"sub" : "s=S8829314B,u=1c0cee38-3a8f-4f8a-83bc-7a0e4c59d6a9", "amr" : [ "pwd", "swk" ],
"iss" : "https://stg-id.singpass.gov.sg",
"exp" : 1618929010,
"iat" : 1618928410,
"nonce" : "CM3Z//K+EgRgZsr/ItsTp8Wu2WxFGh23yKRxYjk2CzA="
}
Based on the sub attribute above, we need to parse those value into a JSON format in order to construct a Socialite User object. We can write the parser as below:
And here is the complete code for our SingPass Login Laravel Socialite Provider
And here is all of the implementation of our Provider, for next part I will cover on how to setup those callback url endpoint, as well as writing a Vue component for Singpass QR Login JS as a wrapper.
Thanks for reading~