Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Singpass Login Laravel Package

accredifysg/singpass-login

Laravel package for SingPass Login, MyInfo, and CorpPass using FAPI 2.0-style auth: OpenID discovery, Pushed Authorization Requests (PAR) with DPoP, PKCE, and private-key JWT client assertions. Includes shared services and thin provider controllers.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require accredifysg/singpass-login
    php artisan vendor:publish --provider="Accredifysg\SingPassLogin\SingPassLoginServiceProvider" --tag="config"
    

    Publish the default listener (optional):

    php artisan vendor:publish --provider="Accredifysg\SingPassLogin\SingPassLoginServiceProvider" --tag="listener"
    
  2. Configure Environment: Add credentials to .env (e.g., SINGPASS_CLIENT_ID, SINGPASS_REDIRECT_URI, SINGPASS_DISCOVERY_ENDPOINT). Ensure NDI_SIGNING_KID and NDI_PRIVATE_JWKS are set for JWT signing.

  3. First Use Case: Trigger a SingPass login from a frontend route:

    async function startSingPassLogin() {
      const res = await fetch('/ndi/sp/login?scopes=openid,name,email');
      const { redirect_url } = await res.json();
      window.location.href = redirect_url;
    }
    

    Handle the callback in your Laravel app (default routes are pre-registered).


Implementation Patterns

Workflows

  1. Authentication Flow:

    • Initiate: Call /ndi/sp/login (or /ndi/mi/initiate for MyInfo) with scopes.
    • Callback: Redirect to /ndi/sp/callback (or /ndi/mi/callback). The package handles:
      • State validation (CSRF protection).
      • Pushed Authorization Request (PAR) with DPoP.
      • Token exchange.
      • UserInfo endpoint calls (if scopes require it).
    • Event Handling: Listen for SingPassSuccessfulLoginEvent (or MyInfoDataRetrievedEvent) to process the response.
  2. Custom Controllers: Override default controllers by setting controller_class in config (e.g., singpass-login.php):

    'controller_class' => \App\Http\Controllers\CustomSingPassLoginController::class,
    
  3. Scope Management:

    • SingPass/MyInfo: Request scopes like openid,name,email,mobileno.
    • CorpPass: Use hierarchical scopes (e.g., entity.identity,user.identity).
    • Validate scopes against available_scopes in config (e.g., config/myinfo.php).
  4. Data Extraction:

    • SingPass: Access nric from SingPassUser (deprecated: $user->getNric()).
    • MyInfo: Use MyInfoDataRetrievedEvent to fetch structured data (e.g., $event->getMyInfoData()['name']['value']).
    • CorpPass: Retrieve entity/actor data via CorpPassUser (e.g., $user->getEntityId(), $user->getIdentityNumber()).
  5. Error Handling:

    • Catch SingPassLoginException, MyInfoRequestException, or CorpPassLoginException in listeners.
    • Log errors via NDI_LOGS_ENABLED=true in config/ndi.php.

Integration Tips

  • Frontend Integration: Use the redirect_url from the /login endpoint to trigger SingPass/MyInfo/CorpPass flows. Ensure credentials: 'same-origin' is set for cookie-based sessions.

    fetch('/ndi/sp/login', { credentials: 'same-origin' })
      .then(res => res.json())
      .then(({ redirect_url }) => window.location.href = redirect_url);
    
  • Backend Processing: Register listeners for events in EventServiceProvider:

    protected $listen = [
        SingPassSuccessfulLoginEvent::class => [
            \App\Listeners\HandleSingPassLogin::class,
        ],
    ];
    
  • Testing: Use SingPass’s sandbox environment for testing. Mock the FapiAuthenticationService or FapiCallbackService in unit tests.

  • Custom Scopes: Extend ProviderConfig to support additional scopes if needed (e.g., for CorpPass’s tpauthinfo).


Gotchas and Tips

Pitfalls

  1. JWKS Configuration:

    • Issue: Missing or invalid NDI_SIGNING_KID/NDI_PRIVATE_JWKS causes DPoP failures.
    • Fix: Generate a JWKS key pair and set:
      NDI_SIGNING_KID="your_kid"
      NDI_PRIVATE_JWKS='{"kty":"EC","kid":"your_kid","d":"private_key_base64","x":"public_key_base64"}'
      
    • Debug: Check /ndi/jwks to verify the endpoint returns valid keys.
  2. Redirect URI Mismatch:

    • Issue: SINGPASS_REDIRECT_URI must exactly match the URI registered with SingPass (including https://).
    • Fix: Validate the URI in config/singpass-login.php and ensure it matches the callback route (e.g., https://yourdomain.com/ndi/sp/callback).
  3. Scope Validation:

    • Issue: Requesting unsupported scopes (e.g., mobileno for MyInfo) throws MyInfoRequestException.
    • Fix: Check available_scopes in config/myinfo.php and update your frontend/backend scope requests accordingly.
  4. State Validation:

    • Issue: Callback failures if state parameter is missing or tampered with.
    • Fix: Ensure the state is preserved across the redirect (handled automatically by the package).
  5. Token Decoding:

    • Issue: Malformed access tokens (non-JWT or missing scope claim) cause UserInfoRequestException.
    • Fix: Validate tokens in FapiCallbackService or log the raw token for debugging.
  6. Event Order:

    • Issue: CorpPassDataRetrievedEvent may fire before CorpPassSuccessfulLoginEvent if UserInfo scopes are requested.
    • Fix: Handle both events in listeners or merge data in a single service.
  7. NRIC Deprecation:

    • Issue: $singPassUser->getNric() is deprecated. Use $singPassUser->nric directly.
    • Fix: Update listeners to use the new property:
      $nric = $event->getSingPassUser()->nric; // Instead of $user->getNric()
      
  8. Discovery Endpoint:

    • Issue: Missing pushed_authorization_request_endpoint in the OpenID discovery response.
    • Fix: Verify SINGPASS_DISCOVERY_ENDPOINT points to the correct FAPI endpoint (e.g., https://id.singpass.gov.sg/fapi/.well-known/openid-configuration).

Debugging Tips

  • Enable Logging: Set NDI_LOGS_ENABLED=true in config/ndi.php to log FAPI requests/responses, token payloads, and errors.

  • Validate Discovery: Manually check the discovery endpoint (e.g., https://id.singpass.gov.sg/fapi/.well-known/openid-configuration) to ensure it includes:

    {
      "pushed_authorization_request_endpoint": "https://id.singpass.gov.sg/fapi/par",
      "token_endpoint": "https://id.singpass.gov.sg/fapi/token",
      "userinfo_endpoint": "https://id.singpass.gov.sg/fapi/userinfo"
    }
    
  • Inspect Tokens: Decode JWTs manually (e.g., jwt.io) to verify claims like scope, sub, or nric. Example:

    echo "PASTE_ACCESS_TOKEN_HERE" | base64 -d | jq
    
  • Test with Postman: Simulate the PAR flow by sending a POST to the pushed_authorization_request_endpoint with a DPoP-proofed request:

    POST /fapi/par HTTP/1.1
    Host: id.singpass.gov.sg
    Content-Type: application/jose+json
    Authorization: DPoP <proof>
    

    Use the FapiAuthenticationService as a reference for the request format.

Extension Points

  1. Custom Providers: Extend the shared FapiAuthenticationService to support additional FAPI 2.0 providers (e.g., custom OIDC endpoints). Override:

    • ProviderConfig for provider-specific settings.
    • FapiCallbackService::shouldCallUserInfo() to customize UserInfo logic.
  2. Token Storage: Store tokens (ID/access) in the session or database by extending SingPassSuccessfulLoginEvent listeners:

    public function handle(SingPassSuccessfulLoginEvent $event) {
        $tokens = $event->getTokens();
        session(['singpass_tokens' => $tokens]);
    }
    
  3. UI Customization:

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
codraw/entity-migrator
codraw/doctrine-extra
codraw/aws-tool-kit
codraw/validator
codraw/workflow
codraw/open-api
codraw/cron-job
codraw/process
codraw/log
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony