## https://sploitus.com/exploit?id=A34AE329-410E-5992-BB12-81AACA691CF1
# SPID SSO POC β Ionic React + Node.js + Signicat Sandbox
A **production-style** proof-of-concept for **SPID (Sistema Pubblico di IdentitΓ Digitale)** authentication in a mobile app, using:
> **Stakeholder documentation:** For architecture, approach, implementation rationale, and Mermaid diagrams (flows, sequence, components), see **[DOCUMENTATION.md](DOCUMENTATION.md)**.
- **Aggregator**: Signicat Sandbox (real SPID test environment)
- **Mobile**: Ionic React (TypeScript) + Cordova (Android)
- **Backend**: Node.js (TypeScript) + Express
- **Auth flow**: OIDC Authorization Code with backend token exchange; backend mints its own JWT for app API calls
The app opens the **system browser** for login; after Signicat redirects to our callback URL, the app can open via **HTTPS App Links** (when host is stable) or via a **custom scheme fallback** (`smartsense://auth/callback`) so it works with ngrokβs changing free URL.
---
## Table of contents
1. [Tools to install](#1-tools-to-install)
2. [Android emulator setup](#2-android-emulator-setup)
3. [Debug keystore fingerprint (for assetlinks.json)](#3-debug-keystore-fingerprint-for-assetlinksjson)
4. [Signicat sandbox setup](#4-signicat-sandbox-setup)
5. [Running the POC](#5-running-the-poc)
6. [Troubleshooting](#6-troubleshooting)
---
## 1. Tools to install
Install the following before running the POC.
| Tool | Purpose | How to install / verify |
|------|--------|--------------------------|
| **Git** | Clone and version control | `git --version` |
| **Node.js + npm** | Backend and frontend build | `node -v` and `npm -v` (you said already installed) |
| **Java JDK 17** | Required for Android SDK / Gradle | Install from [Adoptium](https://adoptium.net/) or your OS package manager. Set `JAVA_HOME` to the JDK root. Verify: `java -version` |
| **Android Studio** | Android SDK, emulator, and build tools | Download from [developer.android.com](https://developer.android.com/studio). Install and run once to complete setup. |
| **Android SDK + Platform Tools** | Build and run Android apps; `adb` for devices/emulators | Via Android Studio: **Settings β Appearance & Behavior β System Settings β Android SDK**. Install **Android SDK Platform** (e.g. API 34) and **Android SDK Platform-Tools**. |
| **ANDROID_HOME** | So Cordova/Gradle can find the SDK | Set to SDK path, e.g. `export ANDROID_HOME=$HOME/Library/Android/sdk` (macOS) or `%LOCALAPPDATA%\Android\Sdk` (Windows). Add to your shell profile. |
| **PATH** | Run `adb` and other tools | Add `$ANDROID_HOME/platform-tools` (and optionally `$ANDROID_HOME/tools`) to `PATH`. |
| **ngrok (free)** | Expose localhost as HTTPS for Signicat redirect and App Links | Install: `brew install ngrok` (macOS) or download from [ngrok.com](https://ngrok.com). Sign up and run `ngrok config add-authtoken `. |
| **Ionic CLI** | Create and run Ionic apps | `npm install -g @ionic/cli` |
| **Cordova** | Add Android platform and plugins | Use `npx cordova` (no global install required) |
Quick checks:
```bash
node -v # e.g. v18+
npm -v
java -version # 17+
echo $ANDROID_HOME
adb version
ngrok version
ionic -v
npx cordova -v
```
---
## 2. Android emulator setup
1. Open **Android Studio**.
2. **Tools β Device Manager** (or **AVD Manager**).
3. **Create Device**: pick a phone (e.g. Pixel 6), then choose a system image (e.g. API 34), download if needed, and finish.
4. Start the emulator from the Device Manager (play button).
5. In a terminal, run:
```bash
adb devices
```
You should see your emulator listed. This is required so you can install and run the Ionic/Cordova app.
---
## 3. Debug keystore fingerprint (for assetlinks.json)
Android App Links require a **Digital Asset Links** file (e.g. `assetlinks.json`) that ties your domain to your appβs signing certificate. For local/dev builds we use the **debug keystore**.
1. Run (on macOS/Linux; Windows use `%USERPROFILE%\.android\debug.keystore`):
```bash
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
```
2. In the output, find **SHA256** under the certificate fingerprint. It looks like:
`AA:BB:CC:DD:...` (with colons).
3. For our backend config we need the **same fingerprint without colons** (e.g. `AABBCCDD...`). You can copy and then remove colons, or use:
```bash
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android 2>/dev/null | grep -A1 "SHA256" | tail -1 | sed 's/.*SHA256: //;s/://g'
```
4. Put this value in `server/.env` as `ANDROID_SHA256_FINGERPRINT=...`. The backend serves `/.well-known/assetlinks.json` using this value so Android can verify App Links.
---
## 4. Signicat sandbox setup
Signicat provides the SPID sandbox. You need a tenant and app configured there.
1. **Sign up / log in** at [Signicat](https://signicat.com) and open the **dashboard** (sandbox if you have a sandbox account).
2. **Create or select an application** (OIDC/OAuth2 client).
3. **Obtain these values** (exact names may vary in the dashboard):
- **SIGNICAT_ISSUER**
Base URL / issuer of your tenant, e.g. `https://api.sandbox.signicat.com/oidc`. This is used for OIDC discovery (e.g. `/.well-known/openid-configuration`).
- **SIGNICAT_CLIENT_ID**
Client ID of your app.
- **SIGNICAT_CLIENT_SECRET**
Client secret of your app.
4. **Redirect URI**
Signicat requires an exact redirect URI. After you start ngrok, your redirect URI will be:
```text
https:///auth/callback
```
Example: `https://abc123.ngrok-free.app/auth/callback`.
Add this **exact** URL in the Signicat appβs redirect URI list. With ngrok free, the domain changes each time you restart ngrok, so you must **update this redirect URI in Signicat** whenever the ngrok URL changes.
5. **Enable SPID** in the sandbox and ensure the **SPID test** identity provider is enabled so you can log in with test credentials.
Put the values in `server/.env` (see [Running the POC](#5-running-the-poc)).
---
## 5. Running the POC
### 5.1 One-time setup
1. **Clone / open** the repo and install dependencies:
```bash
cd ionic-spid-poc-crs
npm install
cd server && npm install && cd ..
cd mobile && npm install && cd ..
```
2. **Backend env**
Copy `server/.env.example` to `server/.env` and fill in:
- `PORT=4000` (change to any port; use the same port when running ngrok)
- `BASE_URL` β leave empty for now; the ngrok script will set it.
- `SIGNICAT_ISSUER`, `SIGNICAT_CLIENT_ID`, `SIGNICAT_CLIENT_SECRET` (from Signicat).
- `ANDROID_PACKAGE_NAME=com.smartsense.spidpoc`
- `ANDROID_SHA256_FINGERPRINT` β SHA256 of debug keystore **without colons** (see section 3).
- `APP_JWT_SECRET` β a long random string for signing our own JWTs (e.g. `openssl rand -hex 32`).
3. **Mobile**
Add Android and install plugins (from repo root or `mobile/`):
```bash
cd mobile
npx ionic build
npx cap add android # or: npx cordova platform add android
npx cordova platform add android
npx cordova plugin add cordova-plugin-inappbrowser
npx cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=smartsense
npm install
cd ..
```
(If you use Capacitor instead of Cordova, adjust: add Android, add InAppBrowser and a custom URL scheme / App Links plugin as per Capacitor docs.)
### 5.2 Start server, ngrok, and update config
1. **Terminal 1 β Backend**
```bash
cd server
npm run dev
```
Server runs on `http://localhost:` (PORT from server/.env, default 4000).
2. **Terminal 2 β ngrok**
```bash
ngrok http
```
Leave it running. Note the HTTPS URL (e.g. `https://abc123.ngrok-free.app`).
3. **Terminal 3 β Update config with ngrok URL**
```bash
node scripts/start-ngrok-and-update.js
```
This script reads the ngrok API (`http://127.0.0.1:4040/api/tunnels`), gets the public HTTPS URL, and writes it to:
- `server/.env` β `BASE_URL=...`
- `mobile/src/config.ts` β `BASE_URL`
It also prints:
- The exact URL to set as **Redirect URI** in Signicat: `https:///auth/callback`
- A link to test: `https:///health`
4. **Update Signicat**
In the Signicat dashboard, set the redirect URI to the printed value (`https:///auth/callback`). Do this again whenever you restart ngrok and the domain changes.
### 5.3 Build and run the mobile app
From the repo root:
```bash
cd mobile
npx ionic build
npx cordova prepare android
npx npx cordova run android
```
Or with Ionic CLI:
```bash
cd mobile
ionic build
ionic cap run android
```
If you use **Cordova** only:
```bash
npx cordova run android
```
The app will install on the emulator or connected device. Tap **Login with SPID**; the system browser opens the backendβs `/auth/spid/start`, which redirects to Signicat. After login, Signicat redirects to `https:///auth/callback`. Use **Continue in app** (custom scheme `smartsense://auth/callback?code=...&state=...`) if the app does not open automatically. The app then exchanges `code`/`state` for your backend JWT and can call `/api/me`.
---
## 6. Troubleshooting
### App Links do not open the app
- **HTTPS App Links** require the **exact host** in the Android intent-filter and in `assetlinks.json`. With ngrok free, the host changes; after running `scripts/start-ngrok-and-update.js` you may need to run `npx cordova prepare android` (or rebuild) so the manifest host matches. Even then, Android caches App Links verification; use the **βContinue in appβ** button (custom scheme `smartsense://`) as the reliable fallback.
- **Custom scheme** (`smartsense://auth/callback`) does not depend on the host and works as long as the app is installed and the plugin is configured.
- Confirm in `config.xml` that the custom URL scheme is `smartsense` and that the intent-filter for `smartsense` is present.
### assetlinks.json not accessible
- The backend serves it at `https:///.well-known/assetlinks.json`. Open that URL in a browser; you should see JSON with `package_name` and `sha256_cert_fingerprints`.
- Ensure `ANDROID_SHA256_FINGERPRINT` in `server/.env` is the **SHA256 of the same keystore you use to build the app** (debug keystore for debug builds), **without colons**.
- Android only uses assetlinks for **https** App Links; custom scheme does not use it.
### Signature mismatch / wrong SHA256
- Ensure you are using the **debug** keystore for debug builds: `~/.android/debug.keystore`, alias `androiddebugkey`, passwords `android`.
- Re-run `keytool -list -v -keystore ~/.android/debug.keystore ...` and copy the SHA256 (without colons) into `ANDROID_SHA256_FINGERPRINT`.
- Rebuild the app and restart the server so the new fingerprint is served.
### Redirect URI mismatch on Signicat
- Signicat compares the redirect URI **exactly**. It must be `https:///auth/callback` with no trailing slash (unless you registered one).
- After every ngrok restart, run `node scripts/start-ngrok-and-update.js` and then **update the redirect URI in the Signicat dashboard** to the new `https:///auth/callback`.
### Android build: Gradle not found
- If `npx cordova build android` fails with βCould not find an installed version of Gradleβ, ensure Android Studio is installed and that Gradle is on your PATH, or open the project in Android Studio once so it installs the Gradle wrapper. Alternatively, install Gradle (e.g. `brew install gradle` on macOS) and ensure itβs in PATH.
### How to view logcat (Android logs)
- With device/emulator connected:
```bash
adb logcat
```
- Filter by your app (package `com.smartsense.spidpoc`):
```bash
adb logcat | grep -i spidpoc
```
- Or filter by Cordova/WebView:
```bash
adb logcat *:S Cordova:V chromium:V
```
### Server or exchange errors
- Check server logs for correlation IDs printed during `/auth/spid/start` and `/auth/exchange`. Use them to trace the same login attempt.
- Ensure `BASE_URL` in `server/.env` matches the ngrok HTTPS URL (no trailing slash). Restart the server after changing `.env`.
- Ensure the app uses the same `BASE_URL` in `mobile/src/config.ts` (run the ngrok update script so both stay in sync).
### Moving to production
- Use a **fixed domain** and set `BASE_URL` to that (e.g. `https://auth.yourcompany.com`).
- In Signicat, register the production redirect URI.
- Use a **reserved ngrok domain** or your own HTTPS host so the callback URL is stable; then HTTPS App Links will work without relying on the custom scheme.
---
## Repository layout (file tree)
```text
ionic-spid-poc-crs/
βββ README.md
βββ package.json # Root scripts: install:all, server, mobile:android, ngrok:update
βββ .gitignore
βββ server/
β βββ src/
β β βββ config.ts # Env validation (zod)
β β βββ index.ts # Express app, mounts routes
β β βββ store.ts # In-memory state/nonce store
β β βββ routes/
β β βββ auth.ts # /auth/spid/start, /auth/callback, /auth/exchange
β β βββ api.ts # /api/me (protected)
β β βββ wellKnown.ts # /.well-known/assetlinks.json
β βββ .env.example
β βββ .env # Created by you; BASE_URL updated by script
β βββ package.json
β βββ tsconfig.json
βββ mobile/
β βββ src/
β β βββ config.ts # BASE_URL (updated by script)
β β βββ App.tsx # handleOpenURL, routes, exchangeAndNavigate
β β βββ main.tsx
β β βββ pages/
β β βββ LoginPage.tsx
β β βββ HomePage.tsx
β βββ config.xml # Cordova: smartsense scheme, HTTPS intent-filter
β βββ index.html
β βββ package.json
β βββ vite.config.ts
β βββ tsconfig.json
β βββ ionic.config.json
βββ scripts/
βββ start-ngrok-and-update.js # Fetches ngrok URL, updates server/.env, mobile/src/config.ts, mobile/config.xml host
βββ update-env-files.js # Manual: BASE_URL=... node scripts/update-env-files.js
```
---
## Flow summary
1. App opens system browser at `https:///auth/spid/start`.
2. Backend creates `state`/`nonce`, stores them, redirects to Signicat authorize URL.
3. User logs in with SPID (Signicat sandbox).
4. Signicat redirects to `https:///auth/callback?code=...&state=...`.
5. Callback page is shown; user can tap βContinue in appβ (`smartsense://auth/callback?code=...&state=...`) so the app opens even if HTTPS App Links fail (e.g. ngrok host change).
6. App sends `code` and `state` to `POST /auth/exchange`; backend exchanges code with Signicat, validates ID token, and issues its **own** JWT.
7. App stores the JWT and calls `GET /api/me` with `Authorization: Bearer ` to show the user profile.
This keeps the app independent of Signicatβs tokens and aligns with production: a stable domain only requires updating `BASE_URL` and Signicat redirect config.
---
## Exact commands to run
```bash
# 1) One-time: install dependencies
npm run install:all
# 2) One-time: copy server env and fill Signicat + ANDROID_SHA256_FINGERPRINT + APP_JWT_SECRET
cp server/.env.example server/.env
# Edit server/.env
# 3) One-time: add Cordova and plugins (from repo root; use npx so Cordova need not be global)
cd mobile
npm run build
npx cordova platform add android
npx cordova plugin add cordova-plugin-inappbrowser
npx cordova plugin add cordova-plugin-customurlscheme --variable URL_SCHEME=smartsense
cd ..
# 4) Start server (Terminal 1)
npm run server
# 5) Start ngrok (Terminal 2)
ngrok http 4000
# 6) Update BASE_URL in server + mobile (Terminal 3)
npm run ngrok:update
# Then update Signicat redirect URI to the printed URL + /auth/callback
# 7) Build and run Android app
cd mobile && npm run build && npx npx cordova run android
```
---
## Final checklist (must work)
- [ ] Server runs on `localhost:` (PORT from .env); ngrok exposes it with an HTTPS URL.
- [ ] Visiting `/auth/spid/start` takes the user to Signicat login.
- [ ] After login, Signicat redirects to `/auth/callback` with `code` and `state`.
- [ ] The callback page appears; it tries to open the app via the same URL (App Links) and shows a βContinue in appβ fallback button using `smartsense://auth/callback?code=...&state=...`.
- [ ] The app receives the callback (via custom scheme when the user taps the fallback).
- [ ] The app calls `POST /auth/exchange` with `{ code, state }` and receives our JWT.
- [ ] The app calls `GET /api/me` with `Authorization: Bearer ` and displays user info.