Share
## 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.