<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Saif Jlassi</title>
    <description>The latest articles on DEV Community by Saif Jlassi (@laakri).</description>
    <link>https://dev.to/laakri</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2553292%2Fa9613969-0e31-4cc3-8a4c-9c35d8f15e6e.png</url>
      <title>DEV Community: Saif Jlassi</title>
      <link>https://dev.to/laakri</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/laakri"/>
    <language>en</language>
    <item>
      <title>Lakron : A Minimal, Encrypted, Realtime Life Scheduler with Supabase</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Wed, 25 Jun 2025 12:59:16 +0000</pubDate>
      <link>https://dev.to/laakri/lakron-a-minimal-encrypted-realtime-life-scheduler-with-supabase-17bi</link>
      <guid>https://dev.to/laakri/lakron-a-minimal-encrypted-realtime-life-scheduler-with-supabase-17bi</guid>
      <description>&lt;p&gt;Lakron: A Minimal, Encrypted, Realtime Life Scheduler with Supabase&lt;/p&gt;

&lt;p&gt;Lakron is a minimal, smart life scheduler that keeps your data private and in sync. Every task is encrypted before it leaves your browser, and realtime sync means your schedule is always up to date everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Encryption Works
&lt;/h2&gt;

&lt;p&gt;Every task you create is encrypted in the browser using a key derived from your password and a salt. Only you can decrypt your data—nobody else, not even the database admin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TaskProvider.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;generateKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useMemo&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;currentProfile&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PBKDF2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ENCRYPTION_SALT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;keySize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;currentProfile&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When saving or loading a task, the title and description are encrypted/decrypted with this key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// supabase.ts&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CryptoJS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Utf8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to Clone and Run Lakron
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   git clone https://github.com/laakri/lakron.git
   cd lakron
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install dependencies:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   npm install
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;.env&lt;/code&gt; file in the root with your Supabase project keys.
&lt;strong&gt;Use your own values, never share your real keys publicly!&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   VITE_ENCRYPTION_KEY=your-random-salt
   VITE_SUPABASE_URL=https://your-project.supabase.co
   VITE_SUPABASE_KEY=your-service-role-key
   VITE_SUPABASE_ANON_KEY=your-anon-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Start the dev server:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable Realtime on the Tasks Table
&lt;/h2&gt;

&lt;p&gt;Lakron uses Supabase Realtime to sync tasks instantly across devices.&lt;br&gt;&lt;br&gt;
To enable this, turn on Realtime for the &lt;code&gt;tasks&lt;/code&gt; table in your Supabase dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to your Supabase project&lt;/li&gt;
&lt;li&gt;Click on "Database" → "Replication"&lt;/li&gt;
&lt;li&gt;Find the &lt;code&gt;tasks&lt;/code&gt; table and enable Realtime&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  SQL to Create the Tables (All in One Block)
&lt;/h2&gt;

&lt;p&gt;If you need to create the &lt;code&gt;profiles&lt;/code&gt; and &lt;code&gt;tasks&lt;/code&gt; tables from scratch, here’s the full SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create profiles table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Create tasks table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;time&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;completed&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'task'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'event'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'task'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;profile_id&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;TIMESTAMP&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;priority&lt;/span&gt; &lt;span class="nb"&gt;INTEGER&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;recurring&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;"completedDates"&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Add constraint to ensure recurrence_rule values are valid&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;
  &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;check_recurrence_rule&lt;/span&gt;
  &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'daily'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'weekly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'monthly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'yearly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'custom'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Optional: Standardize existing recurrence_rule values (safe to run even if table is empty)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'daily'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'daily'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'weekly'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'weekly'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'monthly'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'monthly'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'yearly'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'yearly'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'custom'&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;recurrence_rule&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'daily'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'weekly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'monthly'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'yearly'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Realtime Sync
&lt;/h2&gt;

&lt;p&gt;Supabase’s realtime channels keep tasks in sync across tabs and devices. The provider subscribes to changes for the current profile, so if you add or complete a task on your phone, it updates instantly on your laptop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// TaskProvider.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;unsubscribe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dbOperations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribeToTasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentProfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// handle INSERT, UPDATE, DELETE&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Deploying Lakron is as smooth as using it. The whole process is automated with GitHub Actions, so every push to the main branch triggers a build and deploy.&lt;/p&gt;

&lt;p&gt;The workflow does three things:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Installs dependencies and builds the frontend
&lt;/li&gt;
&lt;li&gt;Copies the built static files to the server using SCP over SSH
&lt;/li&gt;
&lt;li&gt;Reloads the Caddy server so your changes go live instantly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the gist of the deployment workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/deploy.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies and build&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;npm ci&lt;/span&gt;
    &lt;span class="s"&gt;npm run build&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy dist to server&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/scp-action@v0.1.7&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_HOST }}&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_USER }}&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_SSH_KEY }}&lt;/span&gt;
    &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dist/*"&lt;/span&gt;
    &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/youruser/lakron/dist"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reload Caddy&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appleboy/ssh-action@v1.0.0&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_HOST }}&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_USER }}&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.SERVER_SSH_KEY }}&lt;/span&gt;
    &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;sudo systemctl reload caddy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You just need to add your server’s SSH key and connection info as GitHub secrets.&lt;br&gt;&lt;br&gt;
Caddy serves the static files from the &lt;code&gt;dist&lt;/code&gt; directory, so as soon as the workflow finishes, your latest version is live.&lt;/p&gt;

&lt;p&gt;No manual uploads, no downtime, just push and your encrypted, realtime scheduler is up to date for everyone.&lt;/p&gt;




&lt;p&gt;Clone, set your env, run the SQL, enable realtime, and you’ve got a fully encrypted, realtime, minimal life scheduler.&lt;/p&gt;

</description>
      <category>react</category>
      <category>supabase</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Secure Multi-Device Authentication System: The SyncBridge Story #1🔐</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Mon, 23 Dec 2024 18:04:21 +0000</pubDate>
      <link>https://dev.to/laakri/building-a-secure-multi-device-authentication-system-the-syncbridge-story-1-1pgp</link>
      <guid>https://dev.to/laakri/building-a-secure-multi-device-authentication-system-the-syncbridge-story-1-1pgp</guid>
      <description>&lt;p&gt;Ever wondered how apps like WhatsApp Web handle secure device pairing? Or how Netflix manages to keep track of all your devices? Today, I'm diving deep into how I built SyncBridge's authentication system, handling everything from QR code login to token refresh mechanisms. Buckle up - it's gonna be a fun ride! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge 🎯
&lt;/h2&gt;

&lt;p&gt;Building SyncBridge's auth system wasn't just about letting users log in. I needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Track multiple devices per user&lt;/li&gt;
&lt;li&gt;Handle secure device pairing&lt;/li&gt;
&lt;li&gt;Manage token refresh without compromising security&lt;/li&gt;
&lt;li&gt;Implement email verification&lt;/li&gt;
&lt;li&gt;Monitor suspicious activities&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Architecture 🏗️
&lt;/h2&gt;

&lt;p&gt;Here's what I came up with:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkypqu3rvd4oqmox9qrd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjkypqu3rvd4oqmox9qrd.png" alt="Image description" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Secret Sauce 🌟
&lt;/h3&gt;

&lt;p&gt;What makes this system special? Three key ingredients:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Device-Specific Tokens&lt;/strong&gt;: Each device gets its own unique identifier and refresh token. If one device is compromised, others stay safe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rotating Refresh Tokens&lt;/strong&gt;: Every time a refresh token is used, it's invalidated and a new one is issued. This means if someone steals your refresh token, they have exactly one shot at using it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security Event Tracking&lt;/strong&gt;: Every login, token refresh, and device pairing is monitored. Suspicious patterns? We catch them before they become problems.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Let's Get Technical 🛠️
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Token Management: The Dance of Access and Refresh 💃
&lt;/h2&gt;

&lt;p&gt;Remember the last time you stayed logged into an app for weeks without entering your password? That's the magic of refresh tokens. But here's how we made it more secure in SyncBridge:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Two-Token Tango 🎭
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzta6tfm0v0o1tz1yjrpu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzta6tfm0v0o1tz1yjrpu.png" alt="Image description" width="800" height="574"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters 🤔
&lt;/h3&gt;

&lt;p&gt;Here's what makes our token system special:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single-Use Refresh Tokens&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// When refreshing tokens
const deviceAuth = this.deviceAuthRepository.create({
device_id: device.device_id,
refresh_token: refreshToken,
expires_at: new Date(Date.now() + 7 24 60 60 1000), // 7 days
is_valid: true,
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Device-Bound Tokens&lt;/strong&gt;&lt;br&gt;
Each refresh token is tied to a specific device. Try using it from another device? Not happening! 🚫&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Concurrent Request Handling&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (isRefreshing) {
try {
const token = await new Promise((resolve, reject) =&amp;gt; {
failedQueue.push({ resolve, reject });
});
// Handle concurrent requests during refresh
}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Security Checklist ✅
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;✅ Access tokens expire in 15 minutes&lt;/li&gt;
&lt;li&gt;✅ Refresh tokens are single-use&lt;/li&gt;
&lt;li&gt;✅ Device fingerprinting for extra security&lt;/li&gt;
&lt;li&gt;✅ Token rotation on every refresh&lt;/li&gt;
&lt;li&gt;✅ Concurrent request queue management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  QR Code Magic: Secure Device Pairing 📱
&lt;/h2&gt;

&lt;p&gt;One of the coolest features we built is the QR code login system. You know when you open WhatsApp Web and scan that QR code? We built something similar, but with our own security twist!&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works 🔍
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77n1bsuiuqqz4vvowfhn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F77n1bsuiuqqz4vvowfhn.png" alt="Image description" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click "Add Device", here's what happens behind the scenes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async generateLoginQR(userId: string): Promise&amp;lt;{ qrId: string; qrCode:string }&amp;gt; {
// Generate unique QR session ID
const qrId = uuidv4();
// Store QR session with Redis (5 minutes expiration)
await this.redis.set(
qr_login:${qrId},
JSON.stringify({
status: 'pending',
ownerId: userId,
createdAt: new Date().toISOString(),
}),
'EX',300
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security First! 🛡️
&lt;/h3&gt;

&lt;p&gt;Here's how we keep the QR login secure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time-Limited Sessions&lt;/strong&gt;: Each QR code expires in 5 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-Time Use&lt;/strong&gt;: Once scanned, the QR code becomes invalid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device Verification&lt;/strong&gt;: We check if the scanning device is authorized&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time Status Updates&lt;/strong&gt;: Using Redis for instant status changes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggi9e2jir1ekcbvpjoko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggi9e2jir1ekcbvpjoko.png" alt="Image description" width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The User Experience 🎯
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;User opens SyncBridge on their computer&lt;/li&gt;
&lt;li&gt;Clicks "Add Device" and gets a unique QR code&lt;/li&gt;
&lt;li&gt;Scans with their phone (already logged in)&lt;/li&gt;
&lt;li&gt;Both devices get notified of successful pairing&lt;/li&gt;
&lt;li&gt;New device receives its own unique tokens
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Real-time QR status monitoring
const checkQRStatus = async (qrId: string) =&amp;gt; {
const status = await redis.get(qr_login:${qrId});
if (status.authenticated) {
// Handle successful authentication
const { deviceId, tokens } = status;
await setupNewDevice(deviceId, tokens);
}
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Email Verification: Double-Check Everything ✉️
&lt;/h3&gt;

&lt;p&gt;Every new device pairing triggers an email notification:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3ontrehbrkgigi2lcg7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo3ontrehbrkgigi2lcg7.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This gives users instant visibility into their account activity and a chance to revoke access if something looks suspicious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Monitoring: The Guardian Angel 👀
&lt;/h2&gt;

&lt;p&gt;What happens after users log in? We keep watching! Here's our security monitoring system in action:&lt;/p&gt;

&lt;h3&gt;
  
  
  Event Tracking System 📊
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async logSecurityEvent(params: {
user: User,
device: Device,
event_type: SecurityEventType,
severity: SecurityEventSeverity,
}) {
// Log and monitor security events
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What We Track 🔍
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;🚪 Login attempts (successful and failed)&lt;/li&gt;
&lt;li&gt;🔄 Token refresh patterns&lt;/li&gt;
&lt;li&gt;📱 Device pairing activities&lt;/li&gt;
&lt;li&gt;🌍 Geographic anomalies&lt;/li&gt;
&lt;li&gt;⚠️ Suspicious IP changes&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting It All Together 🎯
&lt;/h2&gt;

&lt;p&gt;Here's what makes SyncBridge's authentication special:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Zero Trust Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every request is verified&lt;/li&gt;
&lt;li&gt;No device is inherently trusted&lt;/li&gt;
&lt;li&gt;Continuous validation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User-First Security&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear email notifications&lt;/li&gt;
&lt;li&gt;Device management dashboard&lt;/li&gt;
&lt;li&gt;One-click device revocation&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Future-Proof Design&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scalable token management&lt;/li&gt;
&lt;li&gt;Extensible security events&lt;/li&gt;
&lt;li&gt;Ready for new auth methods&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Final Thoughts 💭
&lt;/h2&gt;

&lt;p&gt;Building a secure authentication system is like building a house - you need a solid foundation (token management), good locks (QR code pairing), and a reliable security system (event monitoring). &lt;/p&gt;

&lt;p&gt;The best part? This is just the beginning. Next up: implementing biometric authentication and hardware key support! 🚀&lt;/p&gt;




&lt;p&gt;Found this helpful? Follow me for more deep dives into web security and full-stack development! &lt;br&gt;
LINKED IN : &lt;a href="https://www.linkedin.com/in/seif-eddine-jlassi/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/seif-eddine-jlassi/&lt;/a&gt;&lt;br&gt;
GITHUB REPO :&lt;a href="https://github.com/laakri/SyncBridge" rel="noopener noreferrer"&gt;https://github.com/laakri/SyncBridge&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building SyncBridge: When "Copy Here, Paste There" Gets an Upgrade 🚀</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Sat, 21 Dec 2024 19:23:54 +0000</pubDate>
      <link>https://dev.to/laakri/building-syncbridge-when-copy-here-paste-there-gets-an-upgrade-n4b</link>
      <guid>https://dev.to/laakri/building-syncbridge-when-copy-here-paste-there-gets-an-upgrade-n4b</guid>
      <description>&lt;p&gt;Ever had that moment when you're reading an article on your phone, but want to continue on your laptop? Or found yourself copying text between devices like a digital juggler? Yeah, me too. That's why I built SyncBridge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62u7jy5a2rej3c2gncaz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F62u7jy5a2rej3c2gncaz.png" alt="Image description" width="660" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Aha!" Moment
&lt;/h2&gt;

&lt;p&gt;Picture this: I'm coding on my desktop, reading docs on my tablet, and messaging on my phone. Copy, switch device, paste. Switch again. Copy again. At that moment, I thought, "This is madness. Why isn't this seamless?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What's SyncBridge, Anyway?
&lt;/h2&gt;

&lt;p&gt;Think of it as a digital bridge between all your devices. You're reading on your phone? Swipe up, continue on your laptop. Found a cool code snippet? It's already in your tablet's clipboard. Writing notes? They sync across devices before you even finish your thought.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssuma5h5p66vasy0o7s4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fssuma5h5p66vasy0o7s4.png" alt="Image description" width="800" height="516"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Secret Sauce 🌶️
&lt;/h2&gt;

&lt;p&gt;What makes SyncBridge different isn't just sync – it's the experience. No more "send to device" buttons or manual syncing. It just works. Like magic, but with better error handling. 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cool Stuff Under the Hood:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Real-time sync that's faster than your coffee break&lt;/li&gt;
&lt;li&gt;Smart device pairing (scan &amp;amp; go)&lt;/li&gt;
&lt;li&gt;Automatic conflict resolution (no more "which version is newer?")&lt;/li&gt;
&lt;li&gt;Battery-friendly background sync&lt;/li&gt;
&lt;li&gt;End-to-end encryption (because your data is yours)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudljoiahy4n14pzlqeu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fudljoiahy4n14pzlqeu9.png" alt="Image description" width="662" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Status: The Foundation
&lt;/h2&gt;

&lt;p&gt;Right now, I've laid down the architecture (shown above) and mapped out the core features. Think of it as building the highway before the cars start rolling. The diagrams show the planned user flows and technical architecture – the blueprint of what's coming.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;This is just the beginning. I'm building SyncBridge in public, and I'll be sharing every major milestone, challenge, and "why didn't I think of this before?" moment. Next up: the real-time sync engine that makes everything feel instantaneous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Join the Journey
&lt;/h2&gt;

&lt;p&gt;Want to see how a "wouldn't it be cool if..." moment turns into a full-fledged product? Follow along! I'll be posting regular updates as SyncBridge grows from these architecture diagrams into your next favorite tool.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>websocket</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Building a Real-Time Auction Platform: Behind the Scenes</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Tue, 10 Dec 2024 17:28:00 +0000</pubDate>
      <link>https://dev.to/laakri/building-a-real-time-auction-platform-behind-the-scenes-5hl6</link>
      <guid>https://dev.to/laakri/building-a-real-time-auction-platform-behind-the-scenes-5hl6</guid>
      <description>&lt;p&gt;As a developer, I recently built a sophisticated auction platform that combines the excitement of live bidding with modern social features. Today, I want to share the journey of creating the core auction experience, particularly focusing on real-time interactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foywsdym36lrv9lkjv5sc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foywsdym36lrv9lkjv5sc.jpg" alt="Image description" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Auction Experience
&lt;/h2&gt;

&lt;p&gt;Our auction platform offers a dynamic, interactive environment where users can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watch live auctions with real-time price updates&lt;/li&gt;
&lt;li&gt;Participate in auction-specific chat rooms&lt;/li&gt;
&lt;li&gt;See live viewer counts&lt;/li&gt;
&lt;li&gt;Place bids with instant feedback&lt;/li&gt;
&lt;li&gt;Track time remaining with precision countdowns&lt;/li&gt;
&lt;li&gt;Interact with other bidders in real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Live Bidding System
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Real-time price updates across all connected users&lt;/li&gt;
&lt;li&gt;Anti-sniping protection&lt;/li&gt;
&lt;li&gt;Minimum increment enforcement&lt;/li&gt;
&lt;li&gt;Instant bid validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Social Integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Live chat during auctions&lt;/li&gt;
&lt;li&gt;Real-time viewer count&lt;/li&gt;
&lt;li&gt;User presence indicators&lt;/li&gt;
&lt;li&gt;Follow/unfollow sellers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Owner Controls
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Real-time auction management&lt;/li&gt;
&lt;li&gt;Chat moderation capabilities&lt;/li&gt;
&lt;li&gt;Early ending option&lt;/li&gt;
&lt;li&gt;Private auction management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Real-Time Architecture
&lt;/h2&gt;

&lt;p&gt;The heart of our platform is the real-time communication system. Here's how we implemented it:&lt;/p&gt;

&lt;h3&gt;
  
  
  WebSocket Implementation
&lt;/h3&gt;

&lt;p&gt;We built a robust WebSocket architecture that handles:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Bidding Events
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Instant price updates&lt;/li&gt;
&lt;li&gt;Bid confirmations&lt;/li&gt;
&lt;li&gt;Outbid notifications&lt;/li&gt;
&lt;li&gt;Current price synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. Room Management
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Auction room joining/leaving&lt;/li&gt;
&lt;li&gt;Viewer count tracking&lt;/li&gt;
&lt;li&gt;User presence management&lt;/li&gt;
&lt;li&gt;Chat room coordination&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. State Synchronization
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Auction status updates&lt;/li&gt;
&lt;li&gt;Timer synchronization&lt;/li&gt;
&lt;li&gt;Owner control changes&lt;/li&gt;
&lt;li&gt;Chat state management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges &amp;amp; Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Race Conditions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Implemented server-side bid validation&lt;/li&gt;
&lt;li&gt;Used atomic operations for price updates&lt;/li&gt;
&lt;li&gt;Maintained bid order consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. State Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Centralized state management for consistency&lt;/li&gt;
&lt;li&gt;Real-time data synchronization&lt;/li&gt;
&lt;li&gt;Optimistic UI updates with rollback&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Performance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Efficient WebSocket message handling&lt;/li&gt;
&lt;li&gt;Minimal payload sizes&lt;/li&gt;
&lt;li&gt;Smart reconnection handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  User Experience Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Responsive Updates
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Instant UI feedback for actions&lt;/li&gt;
&lt;li&gt;Smooth animations for price changes&lt;/li&gt;
&lt;li&gt;Real-time countdown synchronization&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Error Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Graceful connection loss handling&lt;/li&gt;
&lt;li&gt;Automatic reconnection&lt;/li&gt;
&lt;li&gt;State recovery after disconnection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Security
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bid verification&lt;/li&gt;
&lt;li&gt;User authentication&lt;/li&gt;
&lt;li&gt;Rate limiting&lt;/li&gt;
&lt;li&gt;Private auction access control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Insights
&lt;/h2&gt;

&lt;p&gt;The most challenging aspect was maintaining data consistency across all connected clients while ensuring a smooth user experience. We achieved this through:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Event-Driven Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Centralized event handling&lt;/li&gt;
&lt;li&gt;Predictable state updates&lt;/li&gt;
&lt;li&gt;Clean separation of concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Real-Time Data Flow
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bidirectional communication&lt;/li&gt;
&lt;li&gt;Event queuing&lt;/li&gt;
&lt;li&gt;State reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Scalability Considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Horizontal scaling capability&lt;/li&gt;
&lt;li&gt;Connection pooling&lt;/li&gt;
&lt;li&gt;Load balancing ready&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Always handle disconnections gracefully&lt;/li&gt;
&lt;li&gt;Implement proper error boundaries&lt;/li&gt;
&lt;li&gt;Use optimistic UI updates carefully&lt;/li&gt;
&lt;li&gt;Maintain a consistent state across all clients&lt;/li&gt;
&lt;li&gt;Consider latency in user experience design&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This project showcases how modern web technologies can create engaging, real-time experiences. The combination of WebSocket communication, state management, and user experience design creates a platform that's both powerful and user-friendly.&lt;/p&gt;




&lt;h1&gt;
  
  
  #webdev #javascript #typescript #websockets #react
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>typescript</category>
      <category>fastify</category>
    </item>
    <item>
      <title>Building Glide: A Modern E-commerce Platform with Angular 17 and .NET 8</title>
      <dc:creator>Saif Jlassi</dc:creator>
      <pubDate>Tue, 10 Dec 2024 16:40:29 +0000</pubDate>
      <link>https://dev.to/laakri/building-glide-a-modern-e-commerce-platform-with-angular-17-and-net-8-17im</link>
      <guid>https://dev.to/laakri/building-glide-a-modern-e-commerce-platform-with-angular-17-and-net-8-17im</guid>
      <description>&lt;p&gt;Hey fellow developers! Today I'm excited to share my journey building Glide, a modern e-commerce platform that combines cutting-edge technologies with practical solutions. Whether you're a student looking to learn or a developer seeking insights, this post will walk you through the key aspects of building a full-stack e-commerce application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyu7rdr1ytjrxl6vec4nw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyu7rdr1ytjrxl6vec4nw.jpg" alt="Image description" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vision Behind Glide
&lt;/h2&gt;

&lt;p&gt;Every great project starts with a clear vision. For Glide, it was creating an e-commerce platform that not only processes transactions but delivers an exceptional user experience. The goal was to build something that both developers would appreciate for its clean architecture and users would love for its intuitive interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech Stack: Why These Choices Matter
&lt;/h2&gt;

&lt;p&gt;The foundation of Glide rests on two powerful frameworks:&lt;/p&gt;

&lt;p&gt;🔹 &lt;strong&gt;Frontend: Angular 17&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Standalone components for better modularity&lt;/li&gt;
&lt;li&gt;New control flow syntax for cleaner templates&lt;/li&gt;
&lt;li&gt;Built-in SSR capabilities for better SEO&lt;/li&gt;
&lt;li&gt;DaisyUI + Tailwind CSS for responsive design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🔹 &lt;strong&gt;Backend: .NET 8&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Robust API architecture&lt;/li&gt;
&lt;li&gt;JWT + Google OAuth integration&lt;/li&gt;
&lt;li&gt;Entity Framework Core with MySQL&lt;/li&gt;
&lt;li&gt;Real-time capabilities with SignalR&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features That Make Glide Stand Out
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Authentication &amp;amp; Security
&lt;/h3&gt;

&lt;p&gt;The authentication system combines traditional JWT tokens with Google OAuth, providing users with flexible login options while maintaining robust security. The implementation includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure token management&lt;/li&gt;
&lt;li&gt;Role-based access control&lt;/li&gt;
&lt;li&gt;Protected API endpoints&lt;/li&gt;
&lt;li&gt;CORS policy implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Real-Time Features
&lt;/h3&gt;

&lt;p&gt;Using SignalR, we've implemented:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Live notifications&lt;/li&gt;
&lt;li&gt;Order status updates&lt;/li&gt;
&lt;li&gt;Real-time cart synchronization&lt;/li&gt;
&lt;li&gt;Instant messaging between users and support&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Admin Dashboard
&lt;/h3&gt;

&lt;p&gt;A comprehensive admin interface featuring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sales analytics with Chart.js&lt;/li&gt;
&lt;li&gt;Inventory management&lt;/li&gt;
&lt;li&gt;Order processing&lt;/li&gt;
&lt;li&gt;User management&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Development Insights &amp;amp; Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Frontend Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Components are organized by feature&lt;/li&gt;
&lt;li&gt;Services handle state management&lt;/li&gt;
&lt;li&gt;Interceptors manage HTTP requests&lt;/li&gt;
&lt;li&gt;Guards protect restricted routes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Backend Structure
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Clean architecture principles&lt;/li&gt;
&lt;li&gt;Repository pattern for data access&lt;/li&gt;
&lt;li&gt;Middleware for request/response handling&lt;/li&gt;
&lt;li&gt;Structured error handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges &amp;amp; Solutions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Performance Optimization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implemented lazy loading for routes&lt;/li&gt;
&lt;li&gt;Used Angular's built-in optimization tools&lt;/li&gt;
&lt;li&gt;Configured proper caching strategies&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;State Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created service-based state management&lt;/li&gt;
&lt;li&gt;Implemented reactive patterns&lt;/li&gt;
&lt;li&gt;Used BehaviorSubjects for real-time updates&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;typescript&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; // Authentication configuration
&amp;gt; builder.Services.AddAuthentication(options =&amp;gt; {
&amp;gt;     options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
&amp;gt;     options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
&amp;gt; })
&amp;gt; .AddJwtBearer()
&amp;gt; .AddGoogle(options =&amp;gt; {
&amp;gt;     options.Scope.Add("email");
&amp;gt;     options.Scope.Add("profile");
&amp;gt; });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Learning Outcomes
&lt;/h2&gt;

&lt;p&gt;Building Glide taught valuable lessons about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modern web architecture&lt;/li&gt;
&lt;li&gt;Security best practices&lt;/li&gt;
&lt;li&gt;Performance optimization&lt;/li&gt;
&lt;li&gt;User experience design&lt;/li&gt;
&lt;li&gt;Real-time feature implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tips for Students
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Start Small&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Begin with core features&lt;/li&gt;
&lt;li&gt;Add complexity gradually&lt;/li&gt;
&lt;li&gt;Test thoroughly as you go&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focus on Fundamentals&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Master TypeScript/C# basics&lt;/li&gt;
&lt;li&gt;Understand HTTP protocols&lt;/li&gt;
&lt;li&gt;Learn authentication concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Best Practices&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write clean, documented code&lt;/li&gt;
&lt;li&gt;Follow naming conventions&lt;/li&gt;
&lt;li&gt;Use version control effectively&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Future Roadmap
&lt;/h2&gt;

&lt;p&gt;Glide continues to evolve with planned features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI-powered product recommendations&lt;/li&gt;
&lt;li&gt;Enhanced analytics dashboard&lt;/li&gt;
&lt;li&gt;Mobile app development&lt;/li&gt;
&lt;li&gt;Performance optimizations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;The project is open source and welcomes contributions! Whether you're fixing bugs, adding features, or improving documentation, your input is valuable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources for Learning
&lt;/h2&gt;

&lt;p&gt;To build something similar, check out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular Documentation&lt;/li&gt;
&lt;li&gt;.NET 8 Documentation&lt;/li&gt;
&lt;li&gt;JWT Authentication guides&lt;/li&gt;
&lt;li&gt;SignalR tutorials&lt;/li&gt;
&lt;li&gt;DaisyUI components&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connect &amp;amp; Contribute
&lt;/h2&gt;

&lt;p&gt;I believe in learning together and sharing knowledge. If you're interested in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contributing to the project&lt;/li&gt;
&lt;li&gt;Learning more about the implementation&lt;/li&gt;
&lt;li&gt;Discussing web development&lt;/li&gt;
&lt;li&gt;Sharing your own experiences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Feel free to reach out! You can find me on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: [Your GitHub]&lt;/li&gt;
&lt;li&gt;LinkedIn: [Your LinkedIn]&lt;/li&gt;
&lt;li&gt;Twitter: [Your Twitter]&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Remember, every developer started somewhere. Keep learning, keep building, and most importantly, enjoy the process!&lt;/p&gt;

&lt;h1&gt;
  
  
  WebDevelopment #Angular #DotNet #Programming #OpenSource #TechEducation
&lt;/h1&gt;

</description>
      <category>webdev</category>
      <category>angular</category>
      <category>dotnet</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
