<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ionic Vue Archieven - Utilewebsites</title>
	<atom:link href="https://www.utilewebsites.nl/en/section/ionic-vue-android-ios/ionic-vue/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.utilewebsites.nl/en/section/ionic-vue-android-ios/ionic-vue/</link>
	<description></description>
	<lastBuildDate>Sun, 13 Jul 2025 10:18:32 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>
	<item>
		<title>Secure Headless Architecture with Ionic Vue and a PHP JWT API</title>
		<link>https://www.utilewebsites.nl/en/knowledgebase/een-veilige-ionic-vue-app-bouwen-met-een-php-jwt-api/</link>
		
		<dc:creator><![CDATA[UtileWebsites]]></dc:creator>
		<pubDate>Sun, 13 Jul 2025 09:29:17 +0000</pubDate>
				<guid isPermaLink="false">https://www.utilewebsites.nl/knowledgebase/een-veilige-ionic-vue-app-bouwen-met-een-php-jwt-api/</guid>

					<description><![CDATA[<p>A guide to securing headless systems with JSON Web Tokens. Introduction: Why JWT for Headless Security? Headless means your backend exclusively provides data without a presentation layer—for example, a PHP API that only returns JSON to a mobile app or JavaScript frontend. In such a stateless environment, traditional session authentication doesn't work smoothly: the server must process each request independently without tracking user sessions. JSON Web Tokens (JWT) offer a compact, self-contained, and secure way to transmit authorization information. After logging in, the client receives a token that is sent with every API call, allowing your backend to easily verify&#160;<a href="https://www.utilewebsites.nl/en/knowledgebase/een-veilige-ionic-vue-app-bouwen-met-een-php-jwt-api/" class="read-more">Continue Reading</a></p>
<p>Het bericht <a href="https://www.utilewebsites.nl/en/knowledgebase/een-veilige-ionic-vue-app-bouwen-met-een-php-jwt-api/">Secure Headless Architecture with Ionic Vue and a PHP JWT API</a> verscheen eerst op <a href="https://www.utilewebsites.nl/en/">Utilewebsites</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>A guide to securing headless systems with JSON Web Tokens.</p>
<h3 class="wp-block-heading"><strong>Introduction: Why JWT for Headless Security?</strong></h3>
<figure class="wp-block-image size-large is-resized"><a href="https://www.utilewebsites.nl/wp-content/uploads/2025/07/jwt_headless_app_ionic_vue3-1024x877.jpg"><img fetchpriority="high" decoding="async" width="1024" height="877" src="https://www.utilewebsites.nl/wp-content/uploads/2025/07/jwt_headless_app_ionic_vue3-1024x877.jpg" alt="JWT headless app architecture diagram" class="wp-image-5401" style="width:360px;height:auto" srcset="https://www.utilewebsites.nl/wp-content/uploads/2025/07/jwt_headless_app_ionic_vue3-1024x877.jpg 1024w, https://www.utilewebsites.nl/wp-content/uploads/2025/07/jwt_headless_app_ionic_vue3-300x257.jpg 300w, https://www.utilewebsites.nl/wp-content/uploads/2025/07/jwt_headless_app_ionic_vue3-768x658.jpg 768w, https://www.utilewebsites.nl/wp-content/uploads/2025/07/jwt_headless_app_ionic_vue3-1536x1316.jpg 1536w, https://www.utilewebsites.nl/wp-content/uploads/2025/07/jwt_headless_app_ionic_vue3.jpg 2048w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></figure>
<p><strong>Headless</strong> means your backend exclusively provides data without a presentation layer—for example, a PHP API that only returns JSON to a mobile app or JavaScript frontend. In such a stateless environment, traditional session authentication doesn't work smoothly: the server must process each request independently without tracking user sessions.</p>
<p>JSON Web Tokens (JWT) offer a compact, self-contained, and secure way to transmit authorization information. After logging in, the client receives a token that is sent with every API call, allowing your backend to easily verify if the user is authorized.</p>
<h3 class="wp-block-heading"><strong>The Anatomy of a JWT</strong></h3>
<ul class="wp-block-list">
<li><strong>Header:</strong> Contains metadata such as the algorithm (<code>alg</code>) and the token type (<code>typ</code>).</li>
<li><strong>Payload:</strong> Contains the "claims"—data about the user (<code>sub</code>), the issuer (<code>iss</code>), and the expiration (<code>exp</code>). You can also add roles or permissions here.</li>
<li><strong>Signature:</strong> A cryptographic signature over the header and payload, using your secret key, to prevent tampering.</li>
</ul>
<hr class="wp-block-separator has-alpha-channel-opacity"/>
<h3 class="wp-block-heading"><strong>Part 1: The PHP Backend – Building a JWT API</strong></h3>
<p>In this part, we'll build the PHP backend that issues and validates tokens. We will use <code>firebase/php-jwt</code> to create and verify JWTs, ensuring strict claim and algorithm validation.</p>
<ul class="wp-block-list">
<li><strong>Structure:</strong>
<ul class="wp-block-list">
<li><code>api/login.php</code>: Validates login credentials and returns a JWT (access + refresh).</li>
<li><code>api/refresh.php</code>: Refreshes the access token using a valid refresh token.</li>
<li><code>api/secure-data.php</code>: Secure endpoint; only accessible with a valid access token.</li>
<li><code>.env</code>: Contains your secret keys (<code>JWT_SECRET</code> and <code>JWT_REFRESH_SECRET</code>).</li>
</ul>
</li>
<li><strong>Installation:</strong><br />   
<pre class="wp-block-code"><code>composer require firebase/php-jwt</code></pre>
<p>  </li>
</ul>
<h4 class="wp-block-heading"><strong>1. Generating a Token (login.php)</strong></h4>
<pre class="wp-block-code"><code>&lt;?php
require __DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;

// Load secret from .env
$secret       = getenv('JWT_SECRET');
$refreshSecret= getenv('JWT_REFRESH_SECRET');

// Validate user...
if (validUser($_POST['email'], $_POST['password'])) {
    $now = time();
    $accessPayload = [
        'iss' => 'https://your-domain.com',
        'aud' => 'https://your-domain.com',
        'iat' => $now,
        'exp' => $now + 900,      // 15 minutes
        'sub' => getUserId(),
        'role'=> 'user'
    ];
    $refreshPayload = [
        'iss' => 'https://your-domain.com',
        'aud' => 'https://your-domain.com',
        'iat' => $now,
        'exp' => $now + 604800,   // 1 week
        'sub' => getUserId()
    ];
    $accessToken  = JWT::encode($accessPayload, $secret, 'HS256');
    $refreshToken = JWT::encode($refreshPayload, $refreshSecret, 'HS256');
    echo json_encode(['accessToken'=>$accessToken, 'refreshToken'=>$refreshToken]);
    exit;
}
http_response_code(401);
echo json_encode(['error'=>'Invalid credentials']);
</code></pre>
<h4 class="wp-block-heading"><strong>2. Validating a Token (secure-data.php)</strong></h4>
<pre class="wp-block-code"><code>&lt;?php
require __DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$secret = getenv('JWT_SECRET');
$auth   = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (preg_match('/Bearer\s(\S+)/', $auth, $m)) {
    try {
        $token = $m[1];
        $decoded = JWT::decode($token, new Key($secret, 'HS256'));
        // Check iss and aud
        if ($decoded->iss !== 'https://your-domain.com' || $decoded->aud !== 'https://your-domain.com') {
            throw new Exception('Invalid token issuer or audience');
        }
        echo json_encode(['data'=>'Secure information']);
        exit;
    } catch (Exception $e) {
        http_response_code(401);
        echo json_encode(['error'=>'Access denied']);
        exit;
    }
}
http_response_code(401);
echo json_encode(['error'=>'No token found']);
</code></pre>
<h4 class="wp-block-heading"><strong>3. Refresh Endpoint (refresh.php)</strong></h4>
<pre class="wp-block-code"><code>&lt;?php
require __DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

$refreshSecret = getenv('JWT_REFRESH_SECRET');
$body = json_decode(file_get_contents('php://input'), true);
try {
    $decoded = JWT::decode($body['refreshToken'], new Key($refreshSecret, 'HS256'));
    $now = time();
    $newAccess = [
        'iss'=>'https://your-domain.com','aud'=>'https://your-domain.com',
        'iat'=>$now,'exp'=>$now+900,'sub'=>$decoded->sub
    ];
    $token = JWT::encode($newAccess, getenv('JWT_SECRET'), 'HS256');
    echo json_encode(['accessToken'=>$token]);
} catch (Exception $e) {
    http_response_code(401);
    echo json_encode(['error'=>'Invalid refresh token']);
}
</code></pre>
<hr class="wp-block-separator has-alpha-channel-opacity"/>
<h3 class="wp-block-heading"><strong>Part 2: The Ionic Vue App – API Communication with Axios</strong></h3>
<p>In this part, we'll build the frontend in Ionic Vue. We will install Axios, create a service for all our API calls including refresh token rotation, and implement secure storage.</p>
<h4 class="wp-block-heading"><strong>1. Secure Storage &amp; Axios Setup</strong></h4>
<p>First, install the secure storage plugin for Capacitor:</p>
<pre class="wp-block-code"><code>npm install @capacitor-community/secure-storage</code></pre>
<pre class="wp-block-code"><code>// src/services/AxiosService.js
import axios from 'axios';
import { SecureStoragePlugin } from '@capacitor-community/secure-storage';

const API_URL = import.meta.env.VITE_API_URL || 'https://your-domain.com/api';
const storage = new SecureStoragePlugin();

const axiosInstance = axios.create({ baseURL: API_URL, headers:{'Content-Type':'application/json'} });

axiosInstance.interceptors.request.use(async config => {
  const token = await storage.get({ key:'accessToken' });
  if (token.value) config.headers.Authorization = `Bearer ${token.value}`;
  return config;
});

export default axiosInstance;
</code></pre>
<h4 class="wp-block-heading"><strong>2. Login &amp; Token Storage (<code>LoginPage.vue</code>)</strong></h4>
<pre class="wp-block-code"><code>&lt;script setup&gt;
import { SecureStoragePlugin } from '@capacitor-community/secure-storage';
import axiosService from '@/services/AxiosService';
const storage = new SecureStoragePlugin();

async function handleLogin() {
  const resp = await axiosService.post('/login.php', params);
  await storage.set({ key:'accessToken', value:resp.data.accessToken });
  await storage.set({ key:'refreshToken', value:resp.data.refreshToken });
  router.push('/tabs/dashboard');
}
&lt;/script&gt;
</code></pre>
<h4 class="wp-block-heading"><strong>3. Refresh Logic</strong></h4>
<p>In your interceptor, you can automatically make a refresh call on a 401 error:</p>
<pre class="wp-block-code"><code>axiosInstance.interceptors.response.use(null, async error => {
  if (error.response?.status === 401) {
    const refresh = await storage.get({key:'refreshToken'});
    const { data } = await axios.post('/refresh.php', { refreshToken: refresh.value });
    await storage.set({key:'accessToken', value:data.accessToken});
    error.config.headers.Authorization = `Bearer ${data.accessToken}`;
    return axiosInstance.request(error.config);
  }
  return Promise.reject(error);
});</code></pre>
<hr class="wp-block-separator has-alpha-channel-opacity"/>
<h3 class="wp-block-heading"><strong>Best Practices &amp; Security</strong></h3>
<ul class="wp-block-list">
<li>Always use HTTPS for transport security.</li>
<li>Store tokens in secure storage (e.g., Capacitor Secure Storage), not in localStorage.</li>
<li>Implement access and refresh token rotation for short-lived sessions.</li>
<li>Always validate <code>iss</code>, <code>aud</code>, and <code>alg</code> on the server side.</li>
<li>Limit the token payload to the minimum necessary data.</li>
</ul>
<h3 class="wp-block-heading"><strong>Conclusion</strong></h3>
<p>This guide highlights that true security starts with a "security-first" architecture: short-lived tokens, a dual-token strategy, secure client-side storage, and strict server-side validation. With this layered approach, you can build a resilient, production-ready headless application that is resistant to modern threats.</p>
<p>Het bericht <a href="https://www.utilewebsites.nl/en/knowledgebase/een-veilige-ionic-vue-app-bouwen-met-een-php-jwt-api/">Secure Headless Architecture with Ionic Vue and a PHP JWT API</a> verscheen eerst op <a href="https://www.utilewebsites.nl/en/">Utilewebsites</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Steps to Download and Display a PDF in an Ionic Vue App (Android/iOS):</title>
		<link>https://www.utilewebsites.nl/en/knowledgebase/stappen-om-een-pdf-te-downloaden-en-weer-te-geven-in-een-ionic-vue-app-android-ios/</link>
		
		<dc:creator><![CDATA[UtileWebsites]]></dc:creator>
		<pubDate>Sat, 30 Nov 2024 09:20:47 +0000</pubDate>
				<guid isPermaLink="false">https://www.utilewebsites.nl/knowledgebase/stappen-om-een-pdf-te-downloaden-en-weer-te-geven-in-een-ionic-vue-app-android-ios/</guid>

					<description><![CDATA[<p>2. Component Code (Web en Mobile)Add the following code as a component in your Ionic Vue project: 3. Explanation 4. Compatibility</p>
<p>Het bericht <a href="https://www.utilewebsites.nl/en/knowledgebase/stappen-om-een-pdf-te-downloaden-en-weer-te-geven-in-een-ionic-vue-app-android-ios/">Steps to Download and Display a PDF in an Ionic Vue App (Android/iOS):</a> verscheen eerst op <a href="https://www.utilewebsites.nl/en/">Utilewebsites</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full is-resized"><a href="https://www.utilewebsites.nl/wp-content/uploads/2024/11/pdf_donwload_in_app_android_ios.jpg"><img decoding="async" width="411" height="471" src="https://www.utilewebsites.nl/wp-content/uploads/2024/11/pdf_donwload_in_app_android_ios.jpg" alt="" class="wp-image-5158" style="width:306px;height:auto" srcset="https://www.utilewebsites.nl/wp-content/uploads/2024/11/pdf_donwload_in_app_android_ios.jpg 411w, https://www.utilewebsites.nl/wp-content/uploads/2024/11/pdf_donwload_in_app_android_ios-262x300.jpg 262w" sizes="(max-width: 411px) 100vw, 411px" /></a></figure>



<ol class="wp-block-list">
<li><strong><strong>Install Required Packages</strong></strong> Add the necessary libraries to your project:</li>
</ol>



<pre class="wp-block-code"><code>npm install pdfjs-dist @capacitor/filesystem @capacitor-community/http
</code></pre>



<p class="wp-block-paragraph">2. <strong>Component Code (Web en Mobile)</strong><br>Add the following code as a component in your Ionic Vue project:</p>



<pre class="wp-block-code"><code>&lt;template&gt;
  &lt;ion-page&gt;
    &lt;ion-header&gt;
      &lt;ion-toolbar&gt;
        &lt;ion-title&gt;PDF Viewer&lt;/ion-title&gt;
      &lt;/ion-toolbar&gt;
    &lt;/ion-header&gt;
    &lt;ion-content class="ion-padding"&gt;
      &lt;ion-button @click="downloadAndRenderPdf"&gt;Download PDF&lt;/ion-button&gt;
      &lt;!-- PDF.js rendering container --&gt;
      &lt;div v-if="pdfUrl"&gt;
        &lt;canvas ref="pdfCanvas" style="width: 100%;"&gt;&lt;/canvas&gt;
      &lt;/div&gt;
    &lt;/ion-content&gt;
  &lt;/ion-page&gt;
&lt;/template&gt;

&lt;script&gt;
import { defineComponent, watch } from "vue";
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from "@ionic/vue";
import { Filesystem, Directory } from "@capacitor/filesystem";
import { CapacitorHttp } from "@capacitor/core";
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf';

// Set the workerSrc to the imported worker
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
  "pdfjs-dist/build/pdf.worker.min.mjs",
  import.meta.url
).toString();

import { Capacitor } from '@capacitor/core';

// Add detailed platform and path logging
const platform = Capacitor.getPlatform();
console.log('Platform detection:', {
  platform,
  isAndroid: platform === 'android',
  isWeb: platform === 'web'
});

export default defineComponent({
  name: "TestpdfPage",
  components: {
    IonPage,
    IonHeader,
    IonToolbar,
    IonTitle,
    IonContent,
    IonButton,
  },
  data() {
    return {
      pdfUrl: null, // URL of the PDF to be rendered
    };
  },
  methods: {
    // Method to download and render the PDF
    async downloadAndRenderPdf() {
      try {
        console.log("Starting PDF download process...");

        const pdfUrl = "https://example.com/sample.pdf";
        const fileName = "temp.pdf";
        const dirPath = "downloaded";
        const savedFilePath = `${dirPath}/${fileName}`;

        console.log("Downloading PDF using CapacitorHttp from:", pdfUrl);

        // Clean up the existing directory
        try {
          await Filesystem.rmdir({
            path: dirPath,
            directory: Directory.Data,
            recursive: true,
          });
          console.log("Cleaned up existing directory");
        } catch (e) {
          console.log("No existing directory to clean up");
        }

        // Download PDF using native HTTP
        const response = await CapacitorHttp.get({
          url: pdfUrl,
          responseType: "arraybuffer",
        });

        console.log("Download response:", response);

        // Check if response.data is a string (Base64) or an ArrayBuffer
        let base64Data;
        if (typeof response.data === 'string') {
          // Data is already Base64-encoded
          base64Data = response.data;
          console.log("Received data is a Base64 string");
        } else {
          // Convert ArrayBuffer to Base64
          base64Data = this.arrayBufferToBase64(response.data);
          console.log("Converted ArrayBuffer to Base64");
        }

        console.log("Base64 data length:", base64Data.length);

        // Save to device storage
        try {
          await Filesystem.mkdir({
            path: dirPath,
            directory: Directory.Data,
            recursive: true,
          });
          console.log("Created directory for PDF");

          await Filesystem.writeFile({
            path: savedFilePath,
            data: base64Data,
            directory: Directory.Data,
          });

          console.log("File saved locally");

          // Check if the file exists and get its size
          const fileStat = await Filesystem.stat({
            path: savedFilePath,
            directory: Directory.Data,
          });

          console.log("File exists:", fileStat.uri);
          console.log("File size in bytes:", fileStat.size);

          if (fileStat.size === 0) {
            console.error("Downloaded file is empty.");
            return;
          }

          // Set the PDF URL to display it
          this.pdfUrl = "data:application/pdf;base64," + base64Data;
        } catch (fsError) {
          console.error("Filesystem Error:", fsError);
          throw fsError;
        }
      } catch (error) {
        console.error("Operation Failed:", error);
      }
    },
    // Method to render the PDF on a canvas
    async renderPdf() {
      console.log("Starting PDF rendering process...");

      const canvas = this.$refs.pdfCanvas;
      if (!canvas) {
        console.error("Canvas element not found");
        return;
      }
      console.log("Canvas element found");

      const ctx = canvas.getContext("2d");
      if (!ctx) {
        console.error("Failed to get 2D context");
        return;
      }
      console.log("2D context obtained");

      try {
        const pdf = await pdfjsLib.getDocument(this.pdfUrl).promise;
        console.log("PDF document fetched successfully");

        const page = await pdf.getPage(1);
        console.log("Page 1 of PDF document fetched successfully");

        const viewport = page.getViewport({ scale: 1.5 });
        console.log("Viewport created with scale 1.5");

        canvas.width = viewport.width;
        canvas.height = viewport.height;
        console.log("Canvas dimensions set to width:", viewport.width, "height:", viewport.height);

        const renderContext = {
          canvasContext: ctx,
          viewport: viewport,
        };

        console.log("Starting PDF page render");
        await page.render(renderContext).promise;
        console.log("PDF rendered successfully");
      } catch (error) {
        console.error("Error rendering PDF:", JSON.stringify(error, Object.getOwnPropertyNames(error)));
      }
    },
    // Helper method to convert ArrayBuffer to Base64
    arrayBufferToBase64(buffer) {
      let binary = "";
      const bytes = new Uint8Array(buffer);
      const len = bytes.byteLength;
      for (let i = 0; i &lt; len; i++) {
        binary += String.fromCharCode(bytes&#91;i]);
      }
      return btoa(binary);
    },
  },
  watch: {
    // Watcher to render the PDF when the URL changes
    pdfUrl(newUrl) {
      if (newUrl) {
        this.$nextTick(() =&gt; {
          this.renderPdf();
        });
      }
    }
  }
});
&lt;/script&gt;
</code></pre>



<p class="wp-block-paragraph">3. <strong><strong>Explanation</strong></strong></p>



<ul class="wp-block-list">
<li><strong>HTTP Plugin</strong>: Safely downloads files on mobile devices.</li>



<li><strong>Filesystem Plugin</strong>: Saves files locally in the <code>Documents</code> directory, critical for Android/iOS.</li>



<li><strong>PDF.js</strong>: Responsible for rendering PDFs into a canvas element.</li>
</ul>



<p class="wp-block-paragraph">4. <strong><strong>Compatibility</strong></strong></p>



<ul class="wp-block-list">
<li><strong>Android/iOS</strong>: Ensure permissions are set for file access in <code>AndroidManifest.xml</code> and <code>Info.plist</code>.</li>



<li><strong>Web</strong>: PDF.js works directly with external URLs without requiring local storage.</li>
</ul>



<p class="wp-block-paragraph"></p>
<p>Het bericht <a href="https://www.utilewebsites.nl/en/knowledgebase/stappen-om-een-pdf-te-downloaden-en-weer-te-geven-in-een-ionic-vue-app-android-ios/">Steps to Download and Display a PDF in an Ionic Vue App (Android/iOS):</a> verscheen eerst op <a href="https://www.utilewebsites.nl/en/">Utilewebsites</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
