Last active
July 5, 2022 11:07
-
-
Save leeliwei930/f35d5ea70531765e0bdc6c8c6fd2665d to your computer and use it in GitHub Desktop.
Singpass With Laravel Medium Embed
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
SINGPASS_CLIENT_ID="<given-by-singpass-authority>" | |
SINGPASS_REDIRECT_URI="<your-callback-url-that-must-be-same-as-you-provided-for-onboarding>" | |
SINGPASS_SIGNING_KEY="storage/singpass/singpass-signing-key-encrypted.pem" | |
SINGPASS_VERIFICATION_KEY="storage/singpass/singpass-verification-key.pem" | |
SINGPASS_ENCRYPTION_KEY="/storage/singpass/singpass-encryption-key.pem" | |
SINGPASS_DECRYPTION_KEY="/storage/singpass/singpass-decryption-key-encrypted.pem" | |
SINGPASS_WELL_KNOWN_CONFIGURATION_URL="https://stg-id.singpass.gov.sg/.well-known/openid-configuration" | |
SINGPASS_DECRYPTION_KEY_PASSPHRASE="<your-singpass-decryption-key-passphrase>" | |
SINGPASS_SIGNING_KEY_PASSPHRASE="<your-singpass-signing-key-passphrase>" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
Route::get('/singpass-qr-param', "InvokeGenerateSingPassQRParameter@__invoke"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
Route::get('/ndi/singpass/jwks', function () { | |
$keys = \App\Services\SingpassHelper::generateJWKS(); | |
return response()->json($keys); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Providers; | |
use App\Services\SingPassHelper; | |
use Illuminate\Support\ServiceProvider; | |
class AppServiceProvider extends ServiceProvider { | |
public function boot() | |
{ | |
$socialite = $this->app->make('Laravel\Socialite\Contracts\Factory'); | |
$socialite->extend( | |
'singpass', | |
function ($app) use ($socialite) { | |
$config = $app['config']['services.singpass']; | |
return $socialite->buildProvider(SingPassProvider::class, $config); | |
} | |
); | |
} | |
public function register() | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@extends("layouts.app") | |
@section('title') | |
Singpass Login | |
@endsection | |
@section('content') | |
<div class="flex flex-col h-screen items-center justify-center" id="singpass-app"> | |
<singpass-qr-login | |
client-id="{{ config('singpass.client_id') }}" | |
redirect-uri="{{config('singpass.redirect')}}" | |
> | |
</singpass-qr-login> | |
</div> | |
@endsection | |
@section('scripts') | |
<script> | |
Vue.component('singpass-qr-login', { | |
props: { | |
qrParamEndpoint: { | |
type: String, | |
default: "/singpass/api/singpass-qr-param" | |
}, | |
clientId: { | |
type: String, | |
default: "" | |
}, | |
redirectUri: { | |
type: String, | |
default: "" | |
} | |
}, | |
template: ` | |
<div :id="'singpass-qr-login-' + _uid"></div> | |
`, | |
data(){ | |
return { | |
authSession: null, | |
authParams: { | |
state: "", | |
nonce: "" | |
}, | |
qrParams: { | |
clientId: '', | |
redirectUri: '', | |
scope: 'openid', | |
responseType: '' | |
} | |
} | |
}, | |
methods: { | |
loadNDIParams(){ | |
this.$axios.get(this.qrParamEndpoint).then((response) => { | |
if(response.status === 200){ | |
let params = response.data.singpass_qr_param; | |
this.authParams = { | |
state: params.state, | |
nonce: params.nonce | |
} | |
this.qrParams = { | |
clientId: this.clientId, | |
redirectUri: this.redirectUri, | |
scope: 'openid', | |
responseType: params.response_type | |
} | |
this.setupQRCode() | |
} | |
}) | |
}, | |
setupQRCode(){ | |
this.authSession = this.$singpassQR.initAuthSession( | |
`singpass-qr-login-${this._uid}`, | |
{ | |
...this.qrParams | |
}, | |
()=> this.authParams, | |
(errorId, message) => { | |
console.log(`onError. errorId:${errorId} message:${message}`); | |
} | |
); | |
} | |
}, | |
created(){ | |
}, | |
mounted(){ | |
this.loadNDIParams(); | |
}, | |
beforeDestroy(){ | |
if (this.authSession === 'SUCCESSFUL') { | |
this.$singpassQR.cancelAuthSession() | |
} | |
} | |
}) | |
</script> | |
@endsection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Controllers; | |
use Illuminate\Http\Request; | |
use Illuminate\Routing\Controller; | |
use Illuminate\Support\Facades\Session; | |
use Illuminate\Support\Facades\URL; | |
class InvokeGenerateSingpassQRParameter extends Controller { | |
// this code will generate the QRCode parameters that will be used by singpass JS | |
public function __invoke(Request $request) | |
{ | |
$nonce = bin2hex(openssl_random_pseudo_bytes(8)); | |
$state = bin2hex(openssl_random_pseudo_bytes(8)); | |
if(!$request->wantsJson()){ | |
$request->session()->put('state', $state); | |
} | |
$params = [ | |
'client_id' => config('singpass.client_id'), | |
'nonce' => $nonce, | |
'state' => $state, | |
'redirect_uri' => URL::to(config('singpass.redirect_uri')), | |
'response_type' => 'code' | |
]; | |
return response()->json([ | |
'singpass_qr_param' => $params | |
]); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" | |
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<title>@yield('title')</title> | |
<script src="https://unpkg.com/axios/dist/axios.min.js"></script> | |
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> | |
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet"/> | |
<script src="https://stg-id.singpass.gov.sg/static/ndi_embedded_auth.js"></script> | |
</head> | |
<body> | |
<div id="myinfo-app"> | |
@yield('content') | |
</div> | |
@yield('scripts') | |
<script> | |
window.Vue.prototype.$singpassQR = window.NDI; | |
window.Vue.prototype.$axios = window.axios; | |
new Vue({ | |
el: '#singpass-app', | |
}) | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
Route::get('/callback', function(){ | |
$user = \Laravel\Socialite\Facades\Socialite::driver('singpass')->user(); | |
return response()->json($user); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"token": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx", | |
"refreshToken": null, | |
"expiresIn": null, | |
"id": "ed5d300e-0661-43b9-a543-xxxxx", | |
"nickname": null, | |
"name": "singpass-user@ed5d300e-0661-43b9-a543-xxxxx", | |
"email": null, | |
"avatar": null, | |
"user": { | |
"s": "SxxxxxxxB", | |
"u": "ed5d300e-0661-43b9-a543-xxxxx" | |
}, | |
"singpass_user_id": "SxxxxxxxB", | |
"foreigner_id": "", | |
"country_of_issuance": "" | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
return [ | |
'singpass' => [ | |
'well_known_configuration_url' => env('SINGPASS_WELL_KNOWN_CONFIGURATION_URL', ""), | |
'client_id' => env('SINGPASS_CLIENT_ID', ""), | |
'redirect' => env('SINGPASS_REDIRECT_URI', ""), | |
'signing_key' => env('SINGPASS_SIGNING_KEY', ""), | |
'decryption_key' => env('SINGPASS_DECRYPTION_KEY', ""), | |
"signing_key_passphrase" => env("SINGPASS_SIGNING_KEY_PASSPHRASE" , ""), | |
"decryption_key_passphrase" => env("SINGPASS_DECRYPTION_KEY_PASSPHRASE", ""), | |
] | |
]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# encrypt private key use for token decryption | |
openssl ec -in singpass-decryption-key.pem -out singpass-decryption-key-encrypted.pem -aes256 | |
# encrypt private key use to sign the client assertions | |
openssl ec -in singpass-signing-key.pem -out singpass-signing-key-encrypted.pem -aes256 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Services; | |
use Jose\Component\Core\JWKSet; | |
use Jose\Component\KeyManagement\JWKFactory; | |
final class SingpassHelper { | |
public static function generateJWKS() | |
{ | |
$verificationKey = base_path(config('services.singpass.verification_key')); | |
$encryptionKey = base_path(config('services.singpass.encryption_key')); | |
/** import the verification public key file, | |
the second parameter which will be a blank password due to the | |
public key served to be exposed **/ | |
$verifyJWK = JWKFactory::createFromKeyFile( | |
$verificationKey, | |
"", | |
[ | |
'kid' => "acme_verification_key", | |
'use' => 'sig' | |
] | |
); | |
/** import the encryption public key file, | |
the second parameter which will be a blank password due to the | |
public key served to be exposed **/ | |
$encryptionJWK = JWKFactory::createFromKeyFile( | |
$encryptionKey, | |
"", | |
[ | |
'kid' => "acme_enc_key", | |
'use' => 'enc', | |
/** supported algorithm which will be in https://stg-id.singpass.gov.sg/.well-known/openid-configuration, | |
id_token_encryption_alg_values_supported **/ | |
'alg' => "ECDH-ES+A256KW" // tell Singpass API server using ECDH-ES+A256KW to encrypt the content | |
] | |
); | |
// construct new JWK set | |
$keySet = new JWKSet([$verifyJWK, $encryptionJWK]); | |
return $keySet; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Generate a private key for content decryption | |
openssl ecparam -name prime256v1 -genkey -noout -out singpass-decryption-key.pem | |
# Generate a public key for content encryption | |
openssl ec -in singpass-decryption-key.pem -pubout -out singpass-encryption-key.pem | |
# Generate a private key for signature signing key | |
openssl ecparam -name prime256v1 -genkey -noout -out singpass-signing-key.pem | |
# Generate a public key for signature verification key | |
openssl ec -in singpass-signing-key.pem -pubout -out singpass-verification-key.pem |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Socialite; | |
use GuzzleHttp\Exception\ClientException; | |
use GuzzleHttp\Exception\GuzzleException; | |
use GuzzleHttp\Exception\ServerException; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\Log; | |
use Jose\Component\Core\AlgorithmManager; | |
use Jose\Component\Core\AlgorithmManagerFactory; | |
use Jose\Component\Core\JWKSet; | |
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA128KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA192KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256; | |
use Jose\Component\Encryption\Compression\CompressionMethodManager; | |
use Jose\Component\Encryption\Compression\Deflate; | |
use Jose\Component\Encryption\JWEDecrypter; | |
use Jose\Component\Encryption\Serializer\JWESerializerManager; | |
use Jose\Component\KeyManagement\JWKFactory; | |
use Jose\Component\Signature\Algorithm\ES256; | |
use Jose\Component\Signature\Algorithm\ES384; | |
use Jose\Component\Signature\Algorithm\ES512; | |
use Jose\Component\Signature\JWSBuilder; | |
use Jose\Component\Signature\JWSVerifier; | |
use Jose\Component\Signature\Serializer\CompactSerializer; | |
use Jose\Component\Signature\Serializer\JWSSerializerManager; | |
use Laravel\Socialite\Two\AbstractProvider; | |
use Laravel\Socialite\Two\ProviderInterface; | |
use Laravel\Socialite\Two\User; | |
class SingPassProvider extends AbstractProvider implements ProviderInterface{ | |
// Return an Authorization Endpoint | |
protected function getAuthUrl($state) | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
if($this->isStateless()){ | |
return ''; | |
} | |
return $config['authorization_endpoint']; | |
} | |
protected function getTokenUrl() | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
return $config['token_endpoint']; | |
} | |
public function getAccessTokenResponse($code) | |
{ | |
// construct token exchange request | |
try { | |
$clientAssertion = $this->generateClientAssertion(); | |
info("Client Assertion Generated: " . $clientAssertion); | |
$response = $this->getHttpClient()->post($this->getTokenUrl(), [ | |
'headers' => [ | |
'Content-Type' => 'application/x-www-form-urlencoded', | |
'charset' => "ISO-8859-1" | |
], | |
'form_params' => [ | |
'client_id' => config('services.singpass.client_id'), | |
'redirect_uri' => config('services.singpass.redirect'), | |
'grant_type' => 'authorization_code', | |
'code' => $code, | |
'scope' => 'openid', | |
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', | |
'client_assertion' => $clientAssertion | |
] | |
]); | |
$responseBody = json_decode($response->getBody(), true); | |
// due to on next method call getUserByToken only extract the key field | |
// which is access token so we need to replace the value of access token to id_token | |
$responseBody['access_token'] = $responseBody['id_token']; | |
return $responseBody; | |
} | |
catch (ClientException $requestException){ | |
Log::error( $requestException->getMessage()); | |
abort(Response::HTTP_BAD_REQUEST, "Invalid parameter pass in while requesting singpass token"); | |
} catch (ServerException $guzzleException){ | |
// catch if there any internal server error occurred at sinpass | |
$errorResponse = $guzzleException->getResponse()->getBody()->getContents(); | |
$errorResponse = json_decode($errorResponse, true); | |
Log::error('SingPass Internal Server Error' , $errorResponse); | |
abort(Response::HTTP_BAD_GATEWAY, "Unable to login using Singpass right now"); | |
} | |
} | |
// A Client Assertion which replace the needed of client secret | |
public function generateClientAssertion(){ | |
$config = $this->getOpenIDConfiguration(); | |
$signingKeyPassphrase = config('services.singpass.signing_key_passphrase'); | |
// import signature signing key | |
$signingKey = JWKFactory::createFromKeyFile("file://" . base_path(config('services.singpass.signing_key')) , $signingKeyPassphrase); | |
$algorithmFactory = new AlgorithmManagerFactory(); | |
// initiate the algorithm aliases | |
$algorithmFactory->add('ES256' , new ES256()); | |
$algorithmFactory->add('ES384' , new ES384()); | |
$algorithmFactory->add('ES512' , new ES512()); | |
// load all the support signature algorithm based on singpass API openid configuration | |
$algorithmFactory->create($config['token_endpoint_auth_signing_alg_values_supported']); | |
$algorithmManager = new AlgorithmManager($algorithmFactory->all()); | |
$jwsBuilder = new JWSBuilder($algorithmManager); | |
// JWT issue timestamp | |
$issuedAt = now(); | |
//build jwt | |
$jwt = $jwsBuilder->create() | |
->withPayload(json_encode([ | |
'sub' => config('services.singpass.client_id'), | |
'aud' => $config['issuer'], | |
'iss' =>config('services.singpass.client_id'), | |
'iat' => $issuedAt->unix(), | |
'exp' => $issuedAt->addMinutes(2)->unix(), | |
])) // insert claims data | |
->addSignature($signingKey, ['typ' => 'JWT', 'alg' => 'ES256']) // sign it and add the JWS protected header | |
->build(); | |
$serializer = new CompactSerializer(); // The serializer | |
// generate base64 encoded JWT | |
$clientAssertion = $serializer->serialize($jwt, 0); | |
return $clientAssertion; | |
} | |
/** | |
* The id token which is a JWE that is needed to decrypt | |
* @param string $idToken | |
* @return array|string | |
*/ | |
protected function getUserByToken($token) | |
{ | |
$jwe = $token; | |
info("Encrypted JWE: $jwe"); | |
// decrypt the JWE | |
$content = $this->decryptJWE($jwe); | |
info("Decrypted JWE: $content"); | |
if(!is_null($content)){ | |
// verify the content of JWT | |
$jws = $this->verifyTokenSignature($content); | |
if(!$jws){ | |
// abort if signature check failed | |
abort(Response::HTTP_UNAUTHORIZED, "Singpass Signature checking failed"); | |
} | |
return $jws; | |
} else { | |
abort(Response::HTTP_BAD_REQUEST, "Unable to decrypt JWE"); | |
} | |
} | |
private function decryptJWE($idToken){ | |
info($idToken); | |
$config = $this->getOpenIDConfiguration(); | |
$keyEncryptionsAlgo = new AlgorithmManagerFactory(); | |
// create algorithm alias for token encryption that might used by singpass | |
$keyEncryptionsAlgo->add('ECDH-ES+A256KW', new ECDHESA256KW()); | |
$keyEncryptionsAlgo->add('ECDH-ES+A192KW', new ECDHESA192KW()); | |
$keyEncryptionsAlgo->add('ECDH-ES+A128KW', new ECDHESA128KW()); | |
$keyEncryptionsAlgo->add('RSA-OAEP-256', new RSAOAEP256()); | |
$keyEncryptionsAlgo->create($config['id_token_encryption_alg_values_supported'] ?? []); | |
// create algorithm alias for content encryption that used by singpass based on openid configuration | |
$contentEncryptionAlgo = new AlgorithmManagerFactory(); | |
$contentEncryptionAlgo->add('A256CBC-HS512', new A256CBCHS512()); | |
$contentEncryptionAlgo->create($config['id_token_encryption_enc_values_supported'] ?? []); | |
$compressionMethodManager = new CompressionMethodManager([ | |
new Deflate() | |
]); | |
$keyEncryptionAlgorithmManager = new AlgorithmManager($keyEncryptionsAlgo->all()); | |
$contentEncryptionAlgorithmManager = new AlgorithmManager($contentEncryptionAlgo->all()); | |
// create a JWE decrypter | |
$decrypter = new JWEDecrypter( | |
$keyEncryptionAlgorithmManager, | |
$contentEncryptionAlgorithmManager, | |
$compressionMethodManager | |
); | |
$decryptionKeyPassphrase = config('services.singpass.decryption_key_passphrase'); | |
// import decryption key | |
$jwk = JWKFactory::createFromKeyFile("file://" . base_path(config('services.singpass.decryption_key')), $decryptionKeyPassphrase); | |
$serializerManager = new JWESerializerManager([new \Jose\Component\Encryption\Serializer\CompactSerializer()]); | |
$jwe = $serializerManager->unserialize($idToken); | |
// if decryption is success return the decrypted payload | |
if($decrypter->decryptUsingKey($jwe, $jwk, 0)){ | |
info("user: ".$jwe->getPayload()); | |
return $jwe->getPayload(); | |
} | |
return null; | |
} | |
/** | |
* Convert the JWT user claims, separated comma user info to array | |
* @param array $user | |
* @return User | |
*/ | |
protected function mapUserToObject($user) | |
{ | |
$parseUserData = $this->parseUser($user->sub); | |
return (new User)->setRaw($parseUserData)->map([ | |
"singpass_user_id" => $parseUserData['s'] ?? "", | |
"foreigner_id" => $parseUserData['fid'] ?? "", | |
"country_of_issuance" => $parseUserData['coi'] ?? "", | |
"id" => $parseUserData['u'] ?? "", | |
"name" => "singpass-user@".$parseUserData['u'] | |
]); | |
} | |
/** | |
* Split the JWT claims sub and convert to a dictionary type | |
* @param $content | |
* @return array | |
*/ | |
private function parseUser($content){ | |
$processedData = []; | |
$dataRecord = explode("," , $content); | |
foreach ($dataRecord as $record) { | |
$data = explode("=", $record); | |
$processedData[$data[0]] = $data[1] ?? ""; | |
} | |
return $processedData; | |
} | |
/** | |
* Retrieve SingPass API OpenID configuration | |
* @return mixed | |
* @throws GuzzleException | |
*/ | |
public function getOpenIDConfiguration() | |
{ | |
if(Cache::has('singpassOpenIDConfig')){ | |
return Cache::get('singpassOpenIDConfig'); | |
} | |
$response = $this->getHttpClient()->get(config('services.singpass.well_known_configuration_url'), [ | |
'headers' => ['Accept' => 'application/json'] | |
]); | |
$openIDConfig = json_decode($response->getBody(), true); | |
Cache::put('singpassOpenIDConfig', $openIDConfig, now()->addHour()); | |
return $openIDConfig; | |
} | |
public function verifyTokenSignature($token) | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
// load Singpass JWKS | |
$singpassJWKS = $this->retrieveSingPassVerificationKey(); | |
$jwks = JWKSet::createFromJson($singpassJWKS); | |
// select Signature key | |
$verificationKey = $jwks->selectKey('sig'); | |
$signatureAlgo = new AlgorithmManagerFactory(); | |
$signatureAlgo->add('ES256', new ES256()); | |
$signatureAlgo->create($config['id_token_signing_alg_values_supported'] ?? []); | |
$signatureAlgoManager = new AlgorithmManager($signatureAlgo->all()); | |
$serializerManager = new JWSSerializerManager([ | |
new CompactSerializer() | |
]); | |
$jwsVerifier = new JWSVerifier($signatureAlgoManager); | |
$jws = $serializerManager->unserialize($token); | |
$isVerified = $jwsVerifier->verifyWithKey($jws, $verificationKey, 0); | |
return $isVerified ? json_decode($jws->getPayload()) : false; | |
} | |
/** | |
* Load the SingPass API verification key from SingPass JWKS endpoints | |
* @return string | |
* @throws GuzzleException | |
*/ | |
public function retrieveSingPassVerificationKey(): string | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
try { | |
$response = $this->getHttpClient()->get($config['jwks_uri'], [ | |
'headers' => ['Accept' => 'application/json' | |
]]); | |
return $response->getBody()->getContents(); | |
} catch (ServerException $e){ | |
$errorResponse = $e->getResponse()->getBody()->getContents(); | |
$errorResponse = json_decode($errorResponse, true); | |
Log::error('Unable to retrieve Singpass JWKS' , $errorResponse); | |
abort(Response::HTTP_BAD_GATEWAY, "Unable to login using Singpass right now"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Socialite; | |
use GuzzleHttp\Exception\ClientException; | |
use GuzzleHttp\Exception\GuzzleException; | |
use GuzzleHttp\Exception\ServerException; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\Log; | |
use Jose\Component\Core\AlgorithmManager; | |
use Jose\Component\Core\AlgorithmManagerFactory; | |
use Jose\Component\Core\JWKSet; | |
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA128KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA192KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256; | |
use Jose\Component\Encryption\Compression\CompressionMethodManager; | |
use Jose\Component\Encryption\Compression\Deflate; | |
use Jose\Component\Encryption\JWEDecrypter; | |
use Jose\Component\Encryption\Serializer\JWESerializerManager; | |
use Jose\Component\KeyManagement\JWKFactory; | |
use Jose\Component\Signature\Algorithm\ES256; | |
use Jose\Component\Signature\Algorithm\ES384; | |
use Jose\Component\Signature\Algorithm\ES512; | |
use Jose\Component\Signature\JWSBuilder; | |
use Jose\Component\Signature\JWSVerifier; | |
use Jose\Component\Signature\Serializer\CompactSerializer; | |
use Jose\Component\Signature\Serializer\JWSSerializerManager; | |
use Laravel\Socialite\Two\AbstractProvider; | |
use Laravel\Socialite\Two\ProviderInterface; | |
use Laravel\Socialite\Two\User; | |
class SingPassProvider extends AbstractProvider implements ProviderInterface{ | |
// Return an Authorization Endpoint | |
protected function getAuthUrl($state) | |
{ | |
} | |
protected function getTokenUrl() | |
{ | |
} | |
public function getAccessTokenResponse($code) | |
{ | |
} | |
/** | |
* The id token which is a JWE that is needed to decrypt | |
* @param string $idToken | |
* @return array|string | |
*/ | |
protected function getUserByToken($token) | |
{ | |
} | |
/** | |
* Convert the JWT user claims, separated comma user info to array | |
* @param array $user | |
* @return User | |
*/ | |
protected function mapUserToObject($user) | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Socialite; | |
use GuzzleHttp\Exception\ClientException; | |
use GuzzleHttp\Exception\GuzzleException; | |
use GuzzleHttp\Exception\ServerException; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\Log; | |
use Jose\Component\Core\AlgorithmManager; | |
use Jose\Component\Core\AlgorithmManagerFactory; | |
use Jose\Component\Core\JWKSet; | |
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA128KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA192KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256; | |
use Jose\Component\Encryption\Compression\CompressionMethodManager; | |
use Jose\Component\Encryption\Compression\Deflate; | |
use Jose\Component\Encryption\JWEDecrypter; | |
use Jose\Component\Encryption\Serializer\JWESerializerManager; | |
use Jose\Component\KeyManagement\JWKFactory; | |
use Jose\Component\Signature\Algorithm\ES256; | |
use Jose\Component\Signature\Algorithm\ES384; | |
use Jose\Component\Signature\Algorithm\ES512; | |
use Jose\Component\Signature\JWSBuilder; | |
use Jose\Component\Signature\JWSVerifier; | |
use Jose\Component\Signature\Serializer\CompactSerializer; | |
use Jose\Component\Signature\Serializer\JWSSerializerManager; | |
use Laravel\Socialite\Two\AbstractProvider; | |
use Laravel\Socialite\Two\ProviderInterface; | |
use Laravel\Socialite\Two\User; | |
class SingPassProvider extends AbstractProvider implements ProviderInterface{ | |
// ... | |
/** | |
* Retrieve SingPass API OpenID configuration | |
* @return mixed | |
* @throws GuzzleException | |
*/ | |
public function getOpenIDConfiguration() | |
{ | |
if(Cache::has('singpassOpenIDConfig')){ | |
return Cache::get('singpassOpenIDConfig'); | |
} | |
$response = $this->getHttpClient()->get(config('services.singpass.well_known_configuration_url'), [ | |
'headers' => ['Accept' => 'application/json'] | |
]); | |
$openIDConfig = json_decode($response->getBody(), true); | |
Cache::put('singpassOpenIDConfig', $openIDConfig, now()->addHour()); | |
return $openIDConfig; | |
} | |
/** | |
* Load the SingPass API verification key from SingPass JWKS endpoints | |
* @return string | |
* @throws GuzzleException | |
*/ | |
public function retrieveSingPassVerificationKey(): string | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
try { | |
$response = $this->getHttpClient()->get($config['jwks_uri'], [ | |
'headers' => ['Accept' => 'application/json' | |
]]); | |
return $response->getBody()->getContents(); | |
} catch (ServerException $e){ | |
$errorResponse = $e->getResponse()->getBody()->getContents(); | |
$errorResponse = json_decode($errorResponse, true); | |
Log::error('Unable to retrieve Singpass JWKS' , $errorResponse); | |
abort(Response::HTTP_BAD_GATEWAY, "Unable to login using Singpass right now"); | |
} | |
} | |
// A Client Assertion which replace the needed of client secret | |
public function generateClientAssertion(){ | |
$config = $this->getOpenIDConfiguration(); | |
$signingKeyPassphrase = config('services.singpass.signing_key_passphrase'); | |
// import signature signing key | |
$signingKey = JWKFactory::createFromKeyFile("file://" . base_path(config('services.singpass.signing_key')) , $signingKeyPassphrase); | |
$algorithmFactory = new AlgorithmManagerFactory(); | |
// initiate the algorithm aliases | |
$algorithmFactory->add('ES256' , new ES256()); | |
$algorithmFactory->add('ES384' , new ES384()); | |
$algorithmFactory->add('ES512' , new ES512()); | |
// load all the support signature algorithm based on singpass API openid configuration | |
$algorithmFactory->create($config['token_endpoint_auth_signing_alg_values_supported']); | |
$algorithmManager = new AlgorithmManager($algorithmFactory->all()); | |
$jwsBuilder = new JWSBuilder($algorithmManager); | |
// JWT issue timestamp | |
$issuedAt = now(); | |
//build jwt | |
$jwt = $jwsBuilder->create() | |
->withPayload(json_encode([ | |
'sub' => config('services.singpass.client_id'), | |
'aud' => $config['issuer'], | |
'iss' =>config('services.singpass.client_id'), | |
'iat' => $issuedAt->unix(), | |
'exp' => $issuedAt->addMinutes(2)->unix(), | |
])) // insert claims data | |
->addSignature($signingKey, ['typ' => 'JWT', 'alg' => 'ES256']) // sign it and add the JWS protected header | |
->build(); | |
$serializer = new CompactSerializer(); // The serializer | |
// generate base64 encoded JWT | |
$clientAssertion = $serializer->serialize($jwt, 0); | |
return $clientAssertion; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Socialite; | |
use GuzzleHttp\Exception\ClientException; | |
use GuzzleHttp\Exception\GuzzleException; | |
use GuzzleHttp\Exception\ServerException; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\Log; | |
use Jose\Component\Core\AlgorithmManager; | |
use Jose\Component\Core\AlgorithmManagerFactory; | |
use Jose\Component\Core\JWKSet; | |
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA128KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA192KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256; | |
use Jose\Component\Encryption\Compression\CompressionMethodManager; | |
use Jose\Component\Encryption\Compression\Deflate; | |
use Jose\Component\Encryption\JWEDecrypter; | |
use Jose\Component\Encryption\Serializer\JWESerializerManager; | |
use Jose\Component\KeyManagement\JWKFactory; | |
use Jose\Component\Signature\Algorithm\ES256; | |
use Jose\Component\Signature\Algorithm\ES384; | |
use Jose\Component\Signature\Algorithm\ES512; | |
use Jose\Component\Signature\JWSBuilder; | |
use Jose\Component\Signature\JWSVerifier; | |
use Jose\Component\Signature\Serializer\CompactSerializer; | |
use Jose\Component\Signature\Serializer\JWSSerializerManager; | |
use Laravel\Socialite\Two\AbstractProvider; | |
use Laravel\Socialite\Two\ProviderInterface; | |
use Laravel\Socialite\Two\User; | |
class SingPassProvider extends AbstractProvider implements ProviderInterface{ | |
// ... | |
// Due to singpass required RP to integrate their SingpassQRLogin JS, thus redirect to authorization endpoint is not supported | |
protected function getAuthUrl($state) | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
if($this->isStateless()){ | |
return ''; | |
} | |
return $config['authorization_endpoint']; | |
} | |
protected function getTokenUrl() | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
return $config['token_endpoint']; | |
} | |
public function getAccessTokenResponse($code) | |
{ | |
// construct token exchange request | |
try { | |
$clientAssertion = $this->generateClientAssertion(); | |
info("Client Assertion Generated: " . $clientAssertion); | |
$response = $this->getHttpClient()->post($this->getTokenUrl(), [ | |
'headers' => [ | |
'Content-Type' => 'application/x-www-form-urlencoded', | |
'charset' => "ISO-8859-1" | |
], | |
'form_params' => [ | |
'client_id' => config('services.singpass.client_id'), | |
'redirect_uri' => config('services.singpass.redirect'), | |
'grant_type' => 'authorization_code', | |
'code' => $code, | |
'scope' => 'openid', | |
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', | |
'client_assertion' => $clientAssertion | |
] | |
]); | |
$responseBody = json_decode($response->getBody(), true); | |
// due to on next method call getUserByToken only extract the key field | |
// which is access token so we need to replace the value of access token to id_token | |
$responseBody['access_token'] = $responseBody['id_token']; | |
return $responseBody; | |
} | |
catch (ClientException $requestException){ | |
Log::error( $requestException->getMessage()); | |
abort(Response::HTTP_BAD_REQUEST, "Invalid parameter pass in while requesting singpass token"); | |
} catch (ServerException $guzzleException){ | |
// catch if there any internal server error occurred at sinpass | |
$errorResponse = $guzzleException->getResponse()->getBody()->getContents(); | |
$errorResponse = json_decode($errorResponse, true); | |
Log::error('SingPass Internal Server Error' , $errorResponse); | |
abort(Response::HTTP_BAD_GATEWAY, "Unable to login using Singpass right now"); | |
} | |
} | |
/** | |
* The id token which is a JWE that is needed to decrypt | |
* @param string $idToken | |
* @return array|string | |
*/ | |
protected function getUserByToken($token) | |
{ | |
$jwe = $token; | |
info("Encrypted JWE: $jwe"); | |
// decrypt the JWE | |
$content = $this->decryptJWE($jwe); | |
info("Decrypted JWE: $content"); | |
if(!is_null($content)){ | |
// verify the content of JWT | |
$jws = $this->verifyTokenSignature($content); | |
if(!$jws){ | |
// abort if signature check failed | |
abort(Response::HTTP_UNAUTHORIZED, "Singpass Signature checking failed"); | |
} | |
return $jws; | |
} else { | |
abort(Response::HTTP_BAD_REQUEST, "Unable to decrypt JWE"); | |
} | |
} | |
/** | |
* Convert the JWT user claims, separated comma user info to array | |
* @param array $user | |
* @return User | |
*/ | |
protected function mapUserToObject($user) | |
{ | |
$parseUserData = $this->parseUser($user->sub); | |
return (new User)->setRaw($parseUserData)->map([ | |
"singpass_user_id" => $parseUserData['s'] ?? "", | |
"foreigner_id" => $parseUserData['fid'] ?? "", | |
"country_of_issuance" => $parseUserData['coi'] ?? "", | |
"id" => $parseUserData['u'] ?? "", | |
"name" => "singpass-user@".$parseUserData['u'] | |
]); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Socialite; | |
use GuzzleHttp\Exception\ClientException; | |
use GuzzleHttp\Exception\GuzzleException; | |
use GuzzleHttp\Exception\ServerException; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\Log; | |
use Jose\Component\Core\AlgorithmManager; | |
use Jose\Component\Core\AlgorithmManagerFactory; | |
use Jose\Component\Core\JWKSet; | |
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA128KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA192KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256; | |
use Jose\Component\Encryption\Compression\CompressionMethodManager; | |
use Jose\Component\Encryption\Compression\Deflate; | |
use Jose\Component\Encryption\JWEDecrypter; | |
use Jose\Component\Encryption\Serializer\JWESerializerManager; | |
use Jose\Component\KeyManagement\JWKFactory; | |
use Jose\Component\Signature\Algorithm\ES256; | |
use Jose\Component\Signature\Algorithm\ES384; | |
use Jose\Component\Signature\Algorithm\ES512; | |
use Jose\Component\Signature\JWSBuilder; | |
use Jose\Component\Signature\JWSVerifier; | |
use Jose\Component\Signature\Serializer\CompactSerializer; | |
use Jose\Component\Signature\Serializer\JWSSerializerManager; | |
use Laravel\Socialite\Two\AbstractProvider; | |
use Laravel\Socialite\Two\ProviderInterface; | |
use Laravel\Socialite\Two\User; | |
class SingPassProvider extends AbstractProvider implements ProviderInterface{ | |
// ... | |
private function decryptJWE($idToken){ | |
info($idToken); | |
$config = $this->getOpenIDConfiguration(); | |
$keyEncryptionsAlgo = new AlgorithmManagerFactory(); | |
// create algorithm alias for token encryption that might used by singpass | |
$keyEncryptionsAlgo->add('ECDH-ES+A256KW', new ECDHESA256KW()); | |
$keyEncryptionsAlgo->add('ECDH-ES+A192KW', new ECDHESA192KW()); | |
$keyEncryptionsAlgo->add('ECDH-ES+A128KW', new ECDHESA128KW()); | |
$keyEncryptionsAlgo->add('RSA-OAEP-256', new RSAOAEP256()); | |
$keyEncryptionsAlgo->create($config['id_token_encryption_alg_values_supported'] ?? []); | |
// create algorithm alias for content encryption that used by singpass based on openid configuration | |
$contentEncryptionAlgo = new AlgorithmManagerFactory(); | |
$contentEncryptionAlgo->add('A256CBC-HS512', new A256CBCHS512()); | |
$contentEncryptionAlgo->create($config['id_token_encryption_enc_values_supported'] ?? []); | |
$compressionMethodManager = new CompressionMethodManager([ | |
new Deflate() | |
]); | |
$keyEncryptionAlgorithmManager = new AlgorithmManager($keyEncryptionsAlgo->all()); | |
$contentEncryptionAlgorithmManager = new AlgorithmManager($contentEncryptionAlgo->all()); | |
// create a JWE decrypter | |
$decrypter = new JWEDecrypter( | |
$keyEncryptionAlgorithmManager, | |
$contentEncryptionAlgorithmManager, | |
$compressionMethodManager | |
); | |
$decryptionKeyPassphrase = config('services.singpass.decryption_key_passphrase'); | |
// import decryption key | |
$jwk = JWKFactory::createFromKeyFile("file://" . base_path(config('services.singpass.decryption_key')), $decryptionKeyPassphrase); | |
$serializerManager = new JWESerializerManager([new \Jose\Component\Encryption\Serializer\CompactSerializer()]); | |
$jwe = $serializerManager->unserialize($idToken); | |
// if decryption is success return the decrypted payload | |
if($decrypter->decryptUsingKey($jwe, $jwk, 0)){ | |
info("user: ".$jwe->getPayload()); | |
return $jwe->getPayload(); | |
} | |
return null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Socialite; | |
use GuzzleHttp\Exception\ClientException; | |
use GuzzleHttp\Exception\GuzzleException; | |
use GuzzleHttp\Exception\ServerException; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\Log; | |
use Jose\Component\Core\AlgorithmManager; | |
use Jose\Component\Core\AlgorithmManagerFactory; | |
use Jose\Component\Core\JWKSet; | |
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA128KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA192KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256; | |
use Jose\Component\Encryption\Compression\CompressionMethodManager; | |
use Jose\Component\Encryption\Compression\Deflate; | |
use Jose\Component\Encryption\JWEDecrypter; | |
use Jose\Component\Encryption\Serializer\JWESerializerManager; | |
use Jose\Component\KeyManagement\JWKFactory; | |
use Jose\Component\Signature\Algorithm\ES256; | |
use Jose\Component\Signature\Algorithm\ES384; | |
use Jose\Component\Signature\Algorithm\ES512; | |
use Jose\Component\Signature\JWSBuilder; | |
use Jose\Component\Signature\JWSVerifier; | |
use Jose\Component\Signature\Serializer\CompactSerializer; | |
use Jose\Component\Signature\Serializer\JWSSerializerManager; | |
use Laravel\Socialite\Two\AbstractProvider; | |
use Laravel\Socialite\Two\ProviderInterface; | |
use Laravel\Socialite\Two\User; | |
class SingPassProvider extends AbstractProvider implements ProviderInterface{ | |
// ... | |
public function verifyTokenSignature($token) | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
// load Singpass JWKS | |
$singpassJWKS = $this->retrieveSingPassVerificationKey(); | |
$jwks = JWKSet::createFromJson($singpassJWKS); | |
// select Signature key | |
$verificationKey = $jwks->selectKey('sig'); | |
$signatureAlgo = new AlgorithmManagerFactory(); | |
$signatureAlgo->add('ES256', new ES256()); | |
$signatureAlgo->create($config['id_token_signing_alg_values_supported'] ?? []); | |
$signatureAlgoManager = new AlgorithmManager($signatureAlgo->all()); | |
$serializerManager = new JWSSerializerManager([ | |
new CompactSerializer() | |
]); | |
$jwsVerifier = new JWSVerifier($signatureAlgoManager); | |
$jws = $serializerManager->unserialize($token); | |
$isVerified = $jwsVerifier->verifyWithKey($jws, $verificationKey, 0); | |
return $isVerified ? json_decode($jws->getPayload()) : false; | |
} | |
/** | |
* Load the SingPass API verification key from SingPass JWKS endpoints | |
* @return string | |
* @throws GuzzleException | |
*/ | |
public function retrieveSingPassVerificationKey(): string | |
{ | |
$config = $this->getOpenIDConfiguration(); | |
try { | |
$response = $this->getHttpClient()->get($config['jwks_uri'], [ | |
'headers' => ['Accept' => 'application/json' | |
]]); | |
return $response->getBody()->getContents(); | |
} catch (ServerException $e){ | |
$errorResponse = $e->getResponse()->getBody()->getContents(); | |
$errorResponse = json_decode($errorResponse, true); | |
Log::error('Unable to retrieve Singpass JWKS' , $errorResponse); | |
abort(Response::HTTP_BAD_GATEWAY, "Unable to login using Singpass right now"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Socialite; | |
use GuzzleHttp\Exception\ClientException; | |
use GuzzleHttp\Exception\GuzzleException; | |
use GuzzleHttp\Exception\ServerException; | |
use Illuminate\Http\Response; | |
use Illuminate\Support\Facades\Cache; | |
use Illuminate\Support\Facades\Log; | |
use Jose\Component\Core\AlgorithmManager; | |
use Jose\Component\Core\AlgorithmManagerFactory; | |
use Jose\Component\Core\JWKSet; | |
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA128KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA192KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHESA256KW; | |
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256; | |
use Jose\Component\Encryption\Compression\CompressionMethodManager; | |
use Jose\Component\Encryption\Compression\Deflate; | |
use Jose\Component\Encryption\JWEDecrypter; | |
use Jose\Component\Encryption\Serializer\JWESerializerManager; | |
use Jose\Component\KeyManagement\JWKFactory; | |
use Jose\Component\Signature\Algorithm\ES256; | |
use Jose\Component\Signature\Algorithm\ES384; | |
use Jose\Component\Signature\Algorithm\ES512; | |
use Jose\Component\Signature\JWSBuilder; | |
use Jose\Component\Signature\JWSVerifier; | |
use Jose\Component\Signature\Serializer\CompactSerializer; | |
use Jose\Component\Signature\Serializer\JWSSerializerManager; | |
use Laravel\Socialite\Two\AbstractProvider; | |
use Laravel\Socialite\Two\ProviderInterface; | |
use Laravel\Socialite\Two\User; | |
class SingPassProvider extends AbstractProvider implements ProviderInterface{ | |
// ... | |
/** | |
* Split the JWT claims sub and convert to a dictionary type | |
* @param $content | |
* @return array | |
*/ | |
private function parseUser($content){ | |
$processedData = []; | |
$dataRecord = explode("," , $content); | |
foreach ($dataRecord as $record) { | |
$data = explode("=", $record); | |
$processedData[$data[0]] = $data[1] ?? ""; | |
} | |
return $processedData; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
Route::get('/callback', function(){ | |
$user = \Laravel\Socialite\Facades\Socialite::driver('singpass')->stateless()->user(); | |
return response()->json($user); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment