<?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>Forem: Palomino</title>
    <description>The latest articles on Forem by Palomino (@palomino).</description>
    <link>https://forem.com/palomino</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%2F1022880%2F107a5146-e292-4c7e-baa5-3a15110efbd3.jpeg</url>
      <title>Forem: Palomino</title>
      <link>https://forem.com/palomino</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/palomino"/>
    <language>en</language>
    <item>
      <title>Automate your custom sign-in UI deployment with GitHub Actions workflow</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Fri, 20 Sep 2024 01:07:11 +0000</pubDate>
      <link>https://forem.com/logto/automate-your-custom-sign-in-ui-deployment-with-github-actions-workflow-24oe</link>
      <guid>https://forem.com/logto/automate-your-custom-sign-in-ui-deployment-with-github-actions-workflow-24oe</guid>
      <description>&lt;p&gt;Let's show you how to automate the deployment of your custom sign-in UI to Logto Cloud in your devops pipeline with a GitHub Actions workflow.&lt;/p&gt;




&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" rel="noopener noreferrer"&gt;Logto&lt;/a&gt; is your better choice of Customer Identity and Access Management (CIAM) solution. Recently, we launched the "&lt;a href="https://docs.logto.io/docs/recipes/customize-sie/bring-your-ui/" rel="noopener noreferrer"&gt;Bring your own UI&lt;/a&gt;" feature on &lt;a href="http://cloud.logto.io/?sign_up=true" rel="noopener noreferrer"&gt;Logto Cloud&lt;/a&gt;, enabling developers to fully customize their sign-in UI.&lt;/p&gt;

&lt;p&gt;In a &lt;a href="https://blog.logto.io/bring-your-own-ui" rel="noopener noreferrer"&gt;previous blog post&lt;/a&gt;, we also provided a step-to-step guide on creating your own sign-in UI, which includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developing a custom sign-in page with code samples&lt;/li&gt;
&lt;li&gt;Setting up the &lt;code&gt;@logto/tunnel&lt;/code&gt; CLI for local debugging&lt;/li&gt;
&lt;li&gt;Building and zipping your custom UI assets&lt;/li&gt;
&lt;li&gt;Uploading the zip package and deploying to Logto Cloud via the Console UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, as an app developer with a DevOps mindset, you may find this process cumbersome when making changes to your custom sign-in page. Is there a way to automate the entire process?&lt;/p&gt;

&lt;p&gt;We’ve listened to your feedback, and are excited to introduce a new &lt;code&gt;deploy&lt;/code&gt; &lt;a href="https://docs.logto.io/docs/references/tunnel-cli/deploy/" rel="noopener noreferrer"&gt;CLI command&lt;/a&gt; in &lt;code&gt;@logto/tunnel&lt;/code&gt;. This command allows you to automate the deployment process by executing the command in your terminal, or integrating it into a GitHub Actions workflow, which is particularly useful for building your CI/CD pipeline. Let's dive in!&lt;/p&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;Before we dive into the setup, ensure you have the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Logto Cloud account with subscription plan.&lt;/li&gt;
&lt;li&gt;A machine-to-machine application with Management API permissions in your Logto tenant.&lt;/li&gt;
&lt;li&gt;Your project source code should be hosted on GitHub.&lt;/li&gt;
&lt;li&gt;Install &lt;code&gt;@logto/tunnel&lt;/code&gt; CLI tool as a dev dependency in your project.
&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 --save-dev @logto/tunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Step 1: Create a GitHub Actions workflow
&lt;/h1&gt;

&lt;p&gt;In your GitHub repository, create a new workflow file. You can do this by navigating to &lt;code&gt;.github/workflows/&lt;/code&gt; and creating a file named &lt;code&gt;deploy.yml&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy to Logto Cloud

on:
  push:
    branches:
      - main # Change it to "master" if that's the name of your default branch

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm install

      - name: Build
        run: npm run build

      - name: Deploy to Logto Cloud
        env:
          LOGTO_AUTH: ${{ secrets.LOGTO_AUTH }} # Your M2M credentials. E.g. `&amp;lt;m2m-app-id&amp;gt;:&amp;lt;m2m-app-secret&amp;gt;`.
          LOGTO_ENDPOINT: ${{ secrets.LOGTO_ENDPOINT }} # Your Logto endpoint.
          LOGTO_RESOURCE: ${{ secrets.LOGTO_RESOURCE }} # Your Logto resource. Required if custom domain is enabled in your Logto Cloud tenant.
        run: npx logto-tunnel deploy --path ./dist # Change "./dist" to your actual build output folder if necessary.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Explanation of the GitHub Actions workflow
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Trigger: The workflow is triggered on every push to the main branch.&lt;/li&gt;
&lt;li&gt;Jobs: The deploy job runs on the latest Ubuntu environment, and will execute the follow steps.&lt;/li&gt;
&lt;li&gt;Steps:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Checkout code&lt;/strong&gt;: This step checks out your repository code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set up Node.js&lt;/strong&gt;: This step sets up the Node.js environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install dependencies&lt;/strong&gt;: This step installs your project dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build&lt;/strong&gt;: This step builds your project source code into html assets. Let's assuming the output folder is named &lt;code&gt;dist&lt;/code&gt; in the root directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy to Logto Cloud&lt;/strong&gt;: This step runs the Tunnel CLI command to deploy html assets in &lt;code&gt;./dist&lt;/code&gt; directory to your Logto Cloud tenant. It uses environment variables for sensitive information.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;For more information on GitHub Actions. visit the &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Step 2: Configure action secrets in GitHub
&lt;/h1&gt;

&lt;p&gt;To keep your credentials secure, you should store them as secrets in your GitHub repository:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your GitHub repository.&lt;/li&gt;
&lt;li&gt;Click on "&lt;strong&gt;Settings&lt;/strong&gt;".&lt;/li&gt;
&lt;li&gt;Navigate to "&lt;strong&gt;Secrets and variables &amp;gt; Actions&lt;/strong&gt;"&lt;/li&gt;
&lt;li&gt;Click on New repository secret and add the following secrets:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LOGTO_AUTH&lt;/strong&gt;: Your Logto M2M application credentials in the format &lt;code&gt;&amp;lt;m2m-app-id&amp;gt;:&amp;lt;m2m-app-secret&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LOGTO_ENDPOINT&lt;/strong&gt;: Your Logto Cloud endpoint URI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LOGTO_RESOURCE&lt;/strong&gt;: Your Logto Management API resource indicator. Can be found in "API resources -&amp;gt; Logto Management API". Required if custom domain is enabled in your Logto Cloud tenant.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Step 3: Test your workflow
&lt;/h1&gt;

&lt;p&gt;Once you have set up the workflow and configured the secrets, you can test it by merging a PR into the master branch. The GitHub Actions workflow will automatically trigger, and your custom sign-in UI will be deployed to Logto Cloud.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfv0mtfdwrw1mocd6q7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxfv0mtfdwrw1mocd6q7a.png" alt="Image description" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;By integrating the &lt;code&gt;@logto/tunnel&lt;/code&gt; CLI command into your GitHub Actions workflow, you can streamline the deployment process of your custom sign-in UI to Logto Cloud. This automation allows you to focus on development while ensuring that your changes are continuously tested in a live environment.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>design</category>
      <category>githubactions</category>
      <category>opensource</category>
    </item>
    <item>
      <title>5 go-to-market lessons I learned from driving a developer-led growth product</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Thu, 19 Sep 2024 06:41:50 +0000</pubDate>
      <link>https://forem.com/logto/5-go-to-market-lessons-i-learned-from-driving-a-developer-led-growth-product-3ep6</link>
      <guid>https://forem.com/logto/5-go-to-market-lessons-i-learned-from-driving-a-developer-led-growth-product-3ep6</guid>
      <description>&lt;p&gt;Lessons and practices learned in driving Logto’s growth with developers.&lt;/p&gt;




&lt;p&gt;Logto is an open-source, developer-focused product. Here’s our go-to-market timeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We released the open-source version in July 2022.&lt;/li&gt;
&lt;li&gt;In January 2023, we launched the cloud preview (beta cloud).&lt;/li&gt;
&lt;li&gt;By July 2023, it was production-ready with full pricing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Coming from a background in product-led growth for productivity tools, the teams and I tried different go-to-market strategies for Logto. After two years, I’ve reflected on those efforts and the steps we took. I want to share part of that journey and explain why some things didn’t work at the time. These aren’t “mistakes,” but valuable lessons from our experience. I hope these insights help others working on similar projects or startups.&lt;/p&gt;

&lt;h1&gt;
  
  
  Traditional onboarding strategies may not work
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Job-to-be-done onboarding, so users can solve their problems right away.&lt;/li&gt;
&lt;li&gt;During onboarding, include options like “book a call” or “email us” for human outreach to increase conversion rates—something that works well with bigger businesses.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These strategies had been very successful in my past experience. So when Logto launched its cloud-hosted version, I applied them immediately. However, I encountered some confusion and challenges:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What exactly is Logto’s “Job-to-be-done”? Unlike straightforward products like productivity tools (e.g., writing documents or creating artwork), Logto deals with building authentication systems or managing user identities. But how can users achieve this in just a day?&lt;/li&gt;
&lt;li&gt;Sure, we added a Calendly link for scheduling calls, but we didn’t receive many bookings, and it didn’t boost our conversion rate as expected.
&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprtlg4uvc7whtwwahihb.png" alt="Image description" width="800" height="500"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Why #1 Doesn't Work
&lt;/h1&gt;

&lt;p&gt;For developer products, it’s difficult to solve a problem in a short time even if it can be truly self-serve. Even for a single developer, the process involves several stages: integrating with the right tech stack, creating a proof of concept, testing in a development environment, and then moving to production. At any point in this journey, users can drop off. The "job-to-be-done" isn’t a simple, one-step task. Developer needs are often complex, requiring a range of features or technical scenarios that need thoughtful design leveraging our existing capabilities. Solving such problems takes time and can’t be rushed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why #2 Doesn't Work
&lt;/h1&gt;

&lt;p&gt;Looking back, it’s clear why this approach didn’t succeed. When we first launched, our daily sign-ups and organic traffic were quite low. Most of our traffic came from the open-source community, which naturally didn’t bring in larger clients. It’s no surprise we didn’t see bigger clients booking calls during this stage. The low traffic and the source of our users made it unrealistic to expect significant call bookings early on.&lt;/p&gt;

&lt;h1&gt;
  
  
  Freemium or free trial? Before struggling, understand your product first
&lt;/h1&gt;

&lt;p&gt;Choose the model that fits your product, based on the time needed for user activation. Don’t let industry norms restrict you.&lt;/p&gt;

&lt;p&gt;When building a SaaS product, one of the key go-to-market (GTM) questions is whether to choose freemium or a free trial. Common wisdom suggests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Free Trial: Users get full access for a limited time, then must pay to continue. 

&lt;ul&gt;
&lt;li&gt;Best for: Complex or premium products where users need to experience the full features to see the value.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Freemium: Users access a basic version for free, with paid upgrades for advanced features. 

&lt;ul&gt;
&lt;li&gt;Best for: Products with a wide range of features, where free users may upgrade as their needs grow.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We initially chose a freemium model for a quick launch, placing a hard paywall between the free and paid plans. However, developers couldn’t access advanced features like SSO or Organization in the free tier.&lt;/p&gt;

&lt;p&gt;While there’s plenty of debate online about which model is better, it’s crucial to step back and consider your product’s activation timeline. For Logto, we’ve observed that in real world, the testing phase can last up to 6 months. This isn’t due to complexity of Logto but rather the engineer’s work schedule, project planning, team workflows, and other factors we hadn’t anticipated.&lt;/p&gt;

&lt;p&gt;Given this long activation period, it’s important to provide developers full access to all features for testing. That’s why we fully utilize the development tenant to unlock the product’s full capabilities. This is common practice for developer tools but worth noting as it explains why traditional freemium or free trial strategies may not work for us.&lt;/p&gt;

&lt;p&gt;The choice should align with the nature of your product and its adoption timeline. Understand your product’s unique characteristics, and choose a model that fits—not one that pushes users too quickly through your conversion funnel.&lt;/p&gt;

&lt;h1&gt;
  
  
  If you don't understand your customers, your content will come across as self-centered
&lt;/h1&gt;

&lt;p&gt;Executing a GTM with a bottom-up strategy often involves SEO, product marketing, and content marketing. We all know it’s important to start early, so we began creating content right after launching the open-source version. But when we first decided to write articles, I had a big question: &lt;strong&gt;What should I write?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a product designer, my instinct was to write about why we designed certain features, explaining the thought process and philosophy behind them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"In this article, we'll go over the history of Sign-in Experience, including its conception, design decisions, and product tradeoffs. You will also gain a better grasp of how to construct a successful and frictionless sign-in or sign-up experience." - Our blog post 2 years ago &lt;a href="https://blog.logto.io/design-for-seamless-sie-1" rel="noopener noreferrer"&gt;The design considerations for a seamless sign-in experience (First Chapter)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I was eager to share my thoughts on our features and design because I put so much effort into them. But looking back, I realize this is what you'd call "self-absorbed" content. It's common in well-established companies with a strong EPD (Engineering, Product, Design) culture.&lt;/p&gt;

&lt;p&gt;If you’re doing product-led growth (PLG), especially when your product hasn’t yet reached the stage where "people are talking about you," it’s necessary to ensure your content has clear value and a goal for your audience. Avoid being too self-focused.&lt;/p&gt;

&lt;p&gt;For instance, instead of focusing on why a feature was designed, create educational articles that explain specific terms, or tutorials that show how to integrate a cool technology or tool to solve a common technical issue.&lt;/p&gt;

&lt;p&gt;As you learn more about your product and customers, you’ll naturally develop strong opinions about what really matters to your audience. Keeping a “customer-centric” approach will help you improve content quality. Over time, you’ll be able to write content that resonates with your audience. Otherwise, your content risks feeling self-absorbed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Features or best practice
&lt;/h1&gt;

&lt;p&gt;Traditional marketing often emphasizes “best practices” or “solutions” when selling to businesses or individuals, based on the idea that SaaS products primarily drive efficiency and productivity. Many strategies focus on numbers, showing financial savings to highlight benefits. While this can work well for mature companies with a broad customer base, it can be off-putting for developers.&lt;/p&gt;

&lt;p&gt;Two years ago, I focused heavily on building value propositions and crafting a big-picture narrative for our product. However, this approach didn’t always connect with the developer audience.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiatzpdf41ca58uty8gkn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiatzpdf41ca58uty8gkn.png" alt="Image description" width="800" height="436"&gt;&lt;/a&gt;Look at the webpage we had 2 years ago—"Shaping the future"… Wow!&lt;br&gt;
When it comes to satisfying developers and solving their problems, you need to be very practical and grounded. Developers aren’t swayed by lofty benefits; they care about feature availability and flexibility—specifically, how easily your product can integrate into their existing tech stack. If it can’t, don’t try to sell it.&lt;/p&gt;

&lt;p&gt;Now, our content strategy is much more focused. We highlight the specific features we deliver, allowing users to quickly understand what we offer from the first screen, without relying on buzzwords or high-level messages.&lt;/p&gt;
&lt;h1&gt;
  
  
  Is the open-source version a threat to the commercial version?
&lt;/h1&gt;

&lt;p&gt;When we first launched our hosted commercial version, there was always a heated discussion within the team: Would open-source hurt our hosted version since it's free, and our target audience is developers? We also debated whether to limit some of our advanced features in the open-source version.&lt;/p&gt;

&lt;p&gt;So far, we've kept the core features of both the open-source and hosted versions almost the same. The growth of our open-source community has built significant trust with enterprise leads, and more larger businesses are coming onboard. Interestingly, some of these enterprises started out as developers in our community. This has given us a lot of confidence. As long as we continue to build great products, provide excellent services, and help developers solve their problems, whether it's through self-serve growth or direct enterprise sales, success will come naturally.&lt;/p&gt;

&lt;p&gt;In the end, it’s all about solving developer problems, whether through product or service. Stay patient and focused, and everything will fall into place naturally.&lt;/p&gt;

&lt;p&gt;Thanks for reading, and feel free to share your experiences and thoughts if you’re working on something similar!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>identity</category>
      <category>marketing</category>
    </item>
    <item>
      <title>Opaque token vs JWT</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Wed, 11 Sep 2024 06:34:59 +0000</pubDate>
      <link>https://forem.com/logto/opaque-token-vs-jwt-54l7</link>
      <guid>https://forem.com/logto/opaque-token-vs-jwt-54l7</guid>
      <description>&lt;p&gt;Understand the differences between opaque tokens and JWTs, their use cases, and how they are validated in OIDC-based systems.&lt;/p&gt;




&lt;p&gt;In Logto, as an OIDC-based comprehensive CIAM platform, authorization tokens play a crucial role in securing user interactions and managing access to resources. Among the various types of tokens used for authorization, opaque tokens and JWTs (JSON Web Tokens) are the most important.&lt;/p&gt;

&lt;p&gt;We've received several questions from our community, such as: What is the difference between an opaque token and a JWT? Why can't I decode the access token I received, and why does the token length seem short? This blog post aims to clarify these concepts and help you understand the distinctions between opaque tokens and JWTs, their use cases, and why you might encounter different behaviors when working with them.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is an opaque token?
&lt;/h1&gt;

&lt;p&gt;An opaque token is a type of access token that, as the name suggests, is opaque or non-transparent to the client or any external party. This means that the token itself does not carry any readable information about the user or the authorization granted.&lt;/p&gt;

&lt;p&gt;When you receive an opaque token, it often appears as a seemingly random string of characters, and attempting to decode it will yield no meaningful data.&lt;/p&gt;

&lt;p&gt;Here's an example of an opaque token:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Since the actual content of the token is only known to the authorization server that issued it, to validate an opaque token, the client must send it back to the server, which then verifies its authenticity and determines the associated permissions. This approach ensures that sensitive information remains hidden, providing an extra layer of security, but it also requires additional server communication to validate the token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;secure: Opaque tokens do not expose any sensitive information to the client. The token's content is only known to the authorization server.&lt;/li&gt;
&lt;li&gt;revocable: Since the token is stored on the server, and the only way to validate it is through the introspection endpoint on the authorization server, the server can easily revoke the token if needed and prevent unauthorized access.&lt;/li&gt;
&lt;li&gt;small size: Opaque tokens are typically shorter than JWTs, which can be beneficial for performance and storage considerations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stateful: Opaque tokens require the authorization server to maintain state to validate the token, which can introduce additional complexity and overhead.&lt;/li&gt;
&lt;li&gt;performance: The need for additional server communication to validate the token can impact performance, especially in high-traffic scenarios.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What is a JWT?
&lt;/h1&gt;

&lt;p&gt;In contrast to opaque tokens, a JWT (JSON Web Token) is a self-contained, stateless token that carries information in a structured and readable format.&lt;/p&gt;

&lt;p&gt;A JWT is composed of three parts: a &lt;code&gt;header&lt;/code&gt;, a &lt;code&gt;payload&lt;/code&gt;, and a &lt;code&gt;signature&lt;/code&gt;, each encoded in Base64URL.&lt;/p&gt;

&lt;p&gt;Here's an example of a JWT:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;header&lt;/code&gt; contains information about the type of token and the algorithm used for signing. For example, &lt;code&gt;{"alg": "HS256", "typ": "JWT"}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;payload&lt;/code&gt; section contains claims—pieces of information about the user or the authorization—such as user ID, expiration time, and scopes. Because this data is encoded but not encrypted, anyone who has the token can decode it to see the claims, though they cannot alter it without invalidating the signature. Based on the specification and authorization server configuration, various claims can be included in the payload. This gives the token its self-contained nature. For example, &lt;code&gt;{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;signature&lt;/code&gt; is generated by combining the header, payload, and a secret key using the specified algorithm. This signature is used to verify the integrity of the token and ensure that it has not been tampered with.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JWTs are commonly used because they can be verified locally by the client or any service, without needing to interact with the authorization server. This makes JWTs particularly efficient for distributed systems, where multiple services might need to verify the token's authenticity independently.&lt;/p&gt;

&lt;p&gt;However, this convenience also comes with the responsibility of ensuring that the token's claims are not excessively exposed, as they are visible to anyone who has access to the token. Also, JWTs are typically short lived, and the expiration time is included in the token's claims to ensure that the token is not valid indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;stateless: JWTs are self-contained and do not require server-side state to validate.&lt;/li&gt;
&lt;li&gt;cross-service compatibility: JWTs can be easily shared and verified across different services, making them ideal for distributed systems.&lt;/li&gt;
&lt;li&gt;extensible: The payload of a JWT can contain custom claims, allowing for flexible authorization and information sharing.&lt;/li&gt;
&lt;li&gt;standard: JWT tokens follow a well-defined standard (&lt;a href="https://datatracker.ietf.org/doc/html/rfc7662" rel="noopener noreferrer"&gt;RFC 7519&lt;/a&gt;), making them widely supported and interoperable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exposure: The claims in a JWT are visible to anyone who has access to the token, so sensitive information should not be included in the payload.&lt;/li&gt;
&lt;li&gt;large size: JWTs can be larger than opaque tokens due to the additional information they carry, which can impact performance and storage considerations. The claims in a JWT tokens should be kept to a minimum to reduce the token size.&lt;/li&gt;
&lt;li&gt;revocation complexity: Since JWTs are stateless, they are typically valid for a short period of time, and there is no built-in mechanism for revoking tokens before they expire, meaning that a compromised token may remain valid until it expires.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Opaque access token validation
&lt;/h1&gt;

&lt;p&gt;A opaque access token is validated by sending it back to the authorization server for verification. The authorization server maintains the state of issued tokens and can determine the token's validity based on its internal storage.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcksb5dgjrg6p9g86a4c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcksb5dgjrg6p9g86a4c.png" alt="Image description" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client requests an access token from the authorization server.&lt;/li&gt;
&lt;li&gt;The authorization server issues an opaque token.&lt;/li&gt;
&lt;li&gt;The client sends the resource access request with the opaque token in the header.&lt;/li&gt;
&lt;li&gt;The resource provider sends a token introspection request to the authorization server to validate the token.&lt;/li&gt;
&lt;li&gt;The authorization server responds with the token information.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  JWT access token validation (offline)
&lt;/h1&gt;

&lt;p&gt;A JWT access token can be validated offline by the client or any service that has access to the token's public key.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f057esx8rghrvhyzndg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f057esx8rghrvhyzndg.png" alt="Image description" width="800" height="543"&gt;&lt;/a&gt;&lt;br&gt;
0.The resource provider pre-fetches the authorization server's public key from the OIDC discovery endpoint. The public key is used to verify the token's signature and ensure its integrity.&lt;br&gt;
1.The client requests an access token from the authorization server.&lt;br&gt;
2.The authorization server issues a JWT token.&lt;br&gt;
3.The client sends the resource access request with the JWT token in the header.&lt;br&gt;
4.The resource provider decodes and validates the JWT token using the public key obtained from the authorization server.&lt;br&gt;
5.The resource provider grants access based on the token's validity.&lt;/p&gt;
&lt;h1&gt;
  
  
  Use cases in OIDC
&lt;/h1&gt;

&lt;p&gt;In the context of OIDC (OpenID Connect), opaque tokens and JWTs serve different purposes and are used in distinct scenarios.&lt;/p&gt;
&lt;h2&gt;
  
  
  Opaque tokens
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;User profile retrieval:&lt;br&gt;
By default, when a client requests an access token without specifying a resource and includes the &lt;code&gt;openid&lt;/code&gt; scope, the authorization server issues an opaque access token. This token is primarily used to retrieve user profile information from the OIDC &lt;code&gt;/oidc/userinfo&lt;/code&gt; endpoint. Upon receiving a request with the opaque access token, the authorization server checks its internal storage to retrieve the associated authorization information and verifies the token's validity before responding with the user profile details.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Refresh token exchange:&lt;br&gt;
Refresh tokens are designed to be exchanged only between the client and the authorization server, without needing to be shared with resource providers. As such, refresh tokens are typically issued as opaque tokens. When the current access token expires, the client can use the opaque refresh token to obtain a new access token, ensuring continuous access without re-authenticating the user.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  JWTs
&lt;/h2&gt;

&lt;p&gt;1.ID token:&lt;br&gt;
In OIDC, the ID token is a JWT that contains user information and is used to authenticate the user. Typically issued alongside the access token, the ID token allows the client to verify the user's identity. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Decoded payload of an ID token
{
  "iss": "https://logto.io",
  "sub": "1234567890",
  "aud": "client_id",
  "exp": 1630368000,
  "name": "John Doe",
  "email": "john.doe@mail.com",
  "picture": "https://example.com/johndoe.jpg"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
The client can validate the ID token to ensure the user's identity and extract user information for personalization or authorization purposes. ID token is for one-time use only and should not be used for API resource authorization.&lt;/p&gt;

&lt;p&gt;2.API resource access:&lt;br&gt;
When a client requests an access token with a specific resource indicator, the authorization server issues a JWT access token intended for accessing that resource. The JWT contains claims that the resource provider can use to authorize the client's access. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Decoded payload of a JWT access token
{
  "iss": "https://dev.logto.app",
  "sub": "1234567890",
  "aud": "https://api.example.com",
  "scope": "read write",
  "exp": 1630368000
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resource provider can validate the request by checking the claims:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;iss&lt;/code&gt;: Confirms the token was issued by a trusted authorization server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sub:&lt;/code&gt; Identifies the user associated with the token.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aud&lt;/code&gt;: Ensures the token is intended for the specific resource.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scope&lt;/code&gt;: Verifies the permissions granted to the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In summary, opaque tokens and JWTs serve different purposes in OIDC-based systems, with opaque tokens providing a secure and stateful approach to authorization and JWTs offering a self-contained and stateless alternative. Understanding the distinctions between these token types and their use cases is essential for designing secure and efficient authentication and authorization mechanisms in your applications.&lt;/p&gt;

&lt;p&gt;Discover more access token features in Logto:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.logto.io/docs/recipes/rbac/protect-resource/" rel="noopener noreferrer"&gt;Use RBAC to protect your API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.logto.io/docs/recipes/organizations/integration/" rel="noopener noreferrer"&gt;Organization access token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.logto.io/docs/recipes/custom-jwt/" rel="noopener noreferrer"&gt;Custom JWT claims&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>identity</category>
      <category>security</category>
    </item>
    <item>
      <title>Conventional commits won't save your commit messages</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Wed, 11 Sep 2024 03:03:48 +0000</pubDate>
      <link>https://forem.com/logto/conventional-commits-wont-save-your-commit-messages-4ne8</link>
      <guid>https://forem.com/logto/conventional-commits-wont-save-your-commit-messages-4ne8</guid>
      <description>&lt;p&gt;Explore why simply following conventional commits isn't enough to write good commit messages, and introduce key thinking strategies to improve your development process and naturally create meaningful commits.&lt;/p&gt;




&lt;p&gt;A quick search on the internet will reveal a plethora of articles teaching you how to write commit messages. 90% of these articles tell you to use &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/" rel="noopener noreferrer"&gt;conventional commits&lt;/a&gt;. However, a considerable number of developers still struggle with this. They know they should follow these conventions, but they find it difficult to apply them in practice. Every time they commit code, they agonize over whether to write "refactor" or "chore". I've even seen projects where the commit message list consists entirely of the same message: "feat: add page", because writing commit messages was too challenging for them, and they simply gave up.&lt;/p&gt;

&lt;h1&gt;
  
  
  It's too early to talk about conventional commits
&lt;/h1&gt;

&lt;p&gt;I'm not saying that conventional commits are bad. They have many well-known benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They provide a unified format, making commit messages more standardized&lt;/li&gt;
&lt;li&gt;They are easy for automated tools to parse, enabling automatic changelog generation&lt;/li&gt;
&lt;li&gt;They help team members understand the purpose and scope of code changes&lt;/li&gt;
&lt;li&gt;They allow for quick identification of different types of code changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, when a developer is still struggling to write good commit messages even after using conventional commits, throwing an article at them about how to use conventional commits, teaching them what &lt;code&gt;feat&lt;/code&gt; type means, what &lt;code&gt;fix&lt;/code&gt; type means, is actually meaningless. It's like giving an advanced cookbook to someone who doesn't know how to cook, without teaching them how to use basic ingredients and utensils. Yet, such articles are everywhere on the internet. Little do they know, conventional commits can only truly be effective when developers have clear thinking and the ability to divide tasks precisely.&lt;/p&gt;

&lt;p&gt;So, conventional commits aren't bad, but before we get to that, we need to establish the right development mindset and use scientific developer thinking to do things.&lt;/p&gt;

&lt;h1&gt;
  
  
  Key thinking for writing good commit messages
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Code changes should do one thing at a time
&lt;/h2&gt;

&lt;p&gt;In software development, "do one thing at a time" is an important principle. This applies not only to writing code but also to committing code. When we focus on one specific task or change at a time, our code becomes clearer and our intentions more explicit. This method not only improves code readability but also greatly simplifies the code review process. Reviewers can more easily understand and verify the purpose of each change.&lt;/p&gt;

&lt;p&gt;Moreover, this method provides convenience for potential code rollbacks. If problems occur, we can more easily locate and revert specific changes without affecting other unrelated parts. Most importantly, when each change does only one thing, the commit message describing this change naturally becomes more precise and meaningful.&lt;/p&gt;

&lt;p&gt;In practice, this means we need to clearly define the task to be completed before we start coding. After completing a task, we should immediately commit the code, rather than waiting until multiple tasks are completed before committing together. If we discover other areas that need modification while completing the current task, the best approach is to note them down and handle them separately after completing the current task. For example, when you're implementing a feature and discover some existing bugs, what you should do is not fix the bug right away along with your new feature code, but rather implement the feature first, then fix the bug you discovered in another commit, or fix the bug first and then commit the feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commit messages actually exist before writing code
&lt;/h2&gt;

&lt;p&gt;Many developers only start thinking about how to write commit messages after completing the code, but in reality, commit messages should exist before we start coding. This is because commit messages essentially reflect the steps we take to complete a task. In other words, they are our todo items.&lt;/p&gt;

&lt;p&gt;When we start a task, we should first think about what specific steps are needed to complete this task. These steps are our todo list, and at the same time, they are our future commit messages. With this todo list, each of our code changes has a purpose, keeping us focused while coding and preventing us from deviating from the task.&lt;/p&gt;

&lt;p&gt;More importantly, this method can significantly improve our efficiency. When we complete a step, the corresponding commit message is already prepared, and we can use it directly, reducing the time spent on thinking about how to describe the change. At the same time, because these commit messages are completed when we have a comprehensive understanding of the task, they are usually of higher quality and more informative.&lt;/p&gt;

&lt;p&gt;Suppose you need to implement a new user registration feature. You might plan your task steps like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Refactor the existing form component to make it more generic and reusable&lt;/li&gt;
&lt;li&gt;Create a user registration form&lt;/li&gt;
&lt;li&gt;Implement API integration for user registration&lt;/li&gt;
&lt;li&gt;Add unit tests for the user registration feature&lt;/li&gt;
&lt;li&gt;Update documentation for the user registration feature&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The commit messages corresponding to these task steps might be as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;refactor: enhance existing form component for reusability&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feat: create user registration form&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feat: implement user registration API integration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;test: add unit tests for user registration&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs: update documentation for user registration feature&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this way, you have clear task steps and corresponding concise commit messages before you start coding. These commit messages are actually your todo list, guiding you through the entire process of implementing the feature.&lt;/p&gt;

&lt;p&gt;As you complete each step, you can use the corresponding commit message to commit your code. This not only helps you better organize and execute tasks, but also ensures that each commit focuses on a specific step, making the entire development process clearer and more manageable. If you need to slightly adjust the commit message during actual coding, you can make minor modifications to more accurately describe the actual changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conventional commits become a natural choice
&lt;/h2&gt;

&lt;p&gt;When we develop the right development mindset, using conventional Commits becomes natural. At this stage, you'll find that choosing the appropriate type prefix (such as feat, fix, refactor, etc.) becomes effortless because your commit messages already clearly reflect your task steps.&lt;/p&gt;

&lt;p&gt;If you want to learn more about how to use conventional commits, there are many excellent articles available online. But remember, these standards can only truly be effective when built on the foundation of the right development mindset.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Writing good commit messages is not just about following a certain format. It requires us to maintain clear thinking during the development process, clarify the purpose of each change, and consider how to describe our work before we start coding.&lt;/p&gt;

&lt;p&gt;Conventional commits is indeed a useful tool, but it can only exert its maximum effect when combined with the right development mindset. By practicing the two principles of "do one thing at a time" and "think about commit messages in advance", we can not only improve the quality of commit messages but also enhance overall development efficiency and code quality.&lt;/p&gt;

&lt;p&gt;I hope this article helps you rethink your development process and encourages you to try these methods. Remember, good development habits take time to cultivate, but once formed, they will greatly improve your work efficiency and code quality.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>When should I use JWTs?</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Wed, 04 Sep 2024 00:31:43 +0000</pubDate>
      <link>https://forem.com/logto/when-should-i-use-jwts-4hnh</link>
      <guid>https://forem.com/logto/when-should-i-use-jwts-4hnh</guid>
      <description>&lt;p&gt;A comprehensive guide on the pros and cons of using JWTs for authentication, with emphasis on auth provider services like Logto.&lt;/p&gt;




&lt;p&gt;Ah, JSON Web Tokens (JWTs) - the topic that seems to spark a heated debate in the developer community every few months like clockwork! These little tokens have become quite the popular choice for authentication in modern web apps. But here's the thing: while developers love to argue about their pros and cons, the landscape of authentication is constantly evolving. So, let's cut through the noise and take a balanced look at JWTs. We'll explore when they shine, where they might fall short, and help you figure out if they're the right fit for your project.&lt;/p&gt;

&lt;h1&gt;
  
  
  Understanding JWTs
&lt;/h1&gt;

&lt;p&gt;JWTs are compact, self-contained tokens used to securely transmit information between parties as a JSON object. They're commonly used for authentication and information exchange in web development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key features of JWTs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Stateless: They don't require server-side storage&lt;/li&gt;
&lt;li&gt;Portable: Can be used across different domains&lt;/li&gt;
&lt;li&gt;Secure: When implemented correctly, they provide robust security&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  The JWT debate
&lt;/h1&gt;

&lt;p&gt;The controversy surrounding JWTs centers on several key points:&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability vs. complexity
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pro:&lt;/strong&gt; JWTs excel in large-scale, distributed environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Con:&lt;/strong&gt; They can introduce unnecessary complexity for smaller applications.&lt;/p&gt;

&lt;p&gt;JWTs are particularly well-suited for systems that need to handle authentication across multiple servers or services. Their stateless nature means that each server can independently verify the token without needing to consult a centralized session store. This makes JWTs an excellent choice for microservices architectures, cloud-based systems, and applications that require horizontal scaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security considerations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pro:&lt;/strong&gt; JWTs can be securely implemented, especially with auth provider services.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Con:&lt;/strong&gt; Incorrect implementation can lead to vulnerabilities if not using a trusted service.&lt;/p&gt;

&lt;p&gt;When implemented correctly, JWTs offer robust security features. They can be digitally signed to ensure integrity and optionally encrypted to protect sensitive information. However, it's crucial to recognize that a flawed implementation can introduce serious vulnerabilities. Common pitfalls include using weak signing algorithms, improper key management, or failing to validate tokens properly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation challenges
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pro:&lt;/strong&gt; Auth provider services offer simplified, secure JWT implementation. &lt;br&gt;
&lt;strong&gt;Con:&lt;/strong&gt; Secure implementation from scratch can be complex and time-consuming.&lt;/p&gt;

&lt;p&gt;Leveraging auth provider services can significantly reduce the complexity of implementing JWTs. These services handle intricate aspects such as token signing, validation, and cryptographic key management. They often provide well-documented SDKs and APIs, making it easier for developers to integrate secure authentication into their applications. On the flip side, attempting to implement a JWT system from scratch requires a deep understanding of cryptographic principles, secure coding practices, and potential attack vectors, which can be a daunting and time-consuming task for many development teams.&lt;/p&gt;

&lt;p&gt;Auth provider services like Logto have significantly simplified the implementation of JWTs in several ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simplified setup:&lt;/strong&gt; They handle the complexities of JWT implementation, making it accessible even for smaller projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enhanced security:&lt;/strong&gt; These services implement industry-standard security measures, reducing the risk of vulnerabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalability:&lt;/strong&gt; They offer solutions that can grow with your application's needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintenance:&lt;/strong&gt; Regular updates and security patches are handled by the service provider.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By using these services, developers can focus on building their core application features while leaving the intricacies of JWT implementation to the experts.&lt;/p&gt;
&lt;h1&gt;
  
  
  When to use JWTs
&lt;/h1&gt;

&lt;p&gt;JWTs can be particularly beneficial in the following scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Microservices architecture&lt;/strong&gt;: For stateless authentication across multiple services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single sign-on (SSO) systems&lt;/strong&gt;: Enabling access to multiple applications with one authentication.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mobile applications&lt;/strong&gt;: Efficiently maintaining user sessions across API calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-traffic applications&lt;/strong&gt;: Reducing database load in high-volume environments.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-origin resource sharing (CORS)&lt;/strong&gt;: Simplifying authentication across multiple domains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless architectures&lt;/strong&gt;: Providing stateless authentication where server-side sessions are challenging.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Alternatives to consider
&lt;/h1&gt;

&lt;p&gt;For simpler authentication needs, consider these alternatives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Traditional session-based authentication:&lt;/strong&gt; Often sufficient for smaller applications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token-based authentication with server-side storage:&lt;/strong&gt; Combines token flexibility with server-side security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OAuth 2.0 with opaque tokens:&lt;/strong&gt; Suitable for delegated authorization scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API keys:&lt;/strong&gt; For simple machine-to-machine authentication.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;
  
  
  Making the decision
&lt;/h1&gt;

&lt;p&gt;While JWTs offer powerful capabilities, there are situations where they may not be necessary or even advisable:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simple, low-traffic applications:&lt;/strong&gt; For small projects with minimal authentication needs, traditional session-based authentication might be simpler and more than sufficient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Applications with no cross-domain requirements:&lt;/strong&gt; If your application doesn't need to share authentication across multiple domains or services, JWTs may add unnecessary complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projects with limited development resources:&lt;/strong&gt; Implementing JWTs securely from scratch can be resource-intensive. If you lack the expertise or time, simpler alternatives might be more appropriate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Applications with strict security requirements:&lt;/strong&gt; In some cases, server-side sessions might be preferred for their ability to be immediately invalidated, which isn't natively possible with JWTs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scenarios where token size is a concern:&lt;/strong&gt; JWTs can be larger than other token types, which might be an issue in bandwidth-constrained environments.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;However, it's important to note that with the advent of mature authentication tools and services, implementing JWTs has become much more accessible. Services like Logto provide out-of-the-box JWT support with industry-standard security measures, making it feasible for projects of any size to leverage the benefits of JWTs without the associated complexities.&lt;/p&gt;

&lt;p&gt;By using such tools, even smaller projects or those with limited resources can implement robust, scalable authentication systems that can grow with their needs. This approach allows you to focus on your core application logic while benefiting from the flexibility and potential scalability that JWTs offer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>opensource</category>
      <category>identity</category>
    </item>
    <item>
      <title>Bring your own sign-in UI to Logto Cloud</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Tue, 03 Sep 2024 23:39:40 +0000</pubDate>
      <link>https://forem.com/logto/bring-your-own-sign-in-ui-to-logto-cloud-10ed</link>
      <guid>https://forem.com/logto/bring-your-own-sign-in-ui-to-logto-cloud-10ed</guid>
      <description>&lt;p&gt;This tutorial will guide you through the process of creating and deploying your own custom sign-in UI to Logto Cloud.&lt;/p&gt;




&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;Logto provides an out-of-box sign-in experience UI that covers all Logto features, including sign-in, registration, password recovery, single sign-on (SSO), and more. Users can also customize the look and feel of the sign-in experience UI using our "Custom CSS" feature.&lt;/p&gt;

&lt;p&gt;However, some users still want to deeply customize their sign-in experience (both UI and auth flows) to match their branding and specific business requirements. We've heard you! And we're excited to announce that the "Bring your own UI" feature is now available in Logto Cloud.&lt;/p&gt;

&lt;p&gt;In this tutorial, we'll guide you through the steps to create and deploy your own custom sign-in UI to Logto Cloud.&lt;/p&gt;

&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;Before you start, ensure you have the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="http://cloud.logto.io/?sign_up=true" rel="noopener noreferrer"&gt;Logto Cloud&lt;/a&gt; account with a subscription plan&lt;/li&gt;
&lt;li&gt;An application integrated with Logto (&lt;a href="https://docs.logto.io/quick-starts/" rel="noopener noreferrer"&gt;Quick-starts&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.logto.io/docs/references/tunnel-cli/" rel="noopener noreferrer"&gt;Logto tunnel CLI&lt;/a&gt; installed&lt;/li&gt;
&lt;li&gt;Basic knowledge of HTML, CSS, and JavaScript&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simplicity, we will use the classic "username &amp;amp; password" sign-in method in the following steps. Remember to change your sign-in method in Logto Console.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffnyb0fvu8lrrre3nfbsr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffnyb0fvu8lrrre3nfbsr.png" alt="Image description" width="800" height="905"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  Create your custom sign-in UI
&lt;/h1&gt;

&lt;p&gt;The minimum requirement for a sign-in UI is to have an &lt;code&gt;index.html&lt;/code&gt; file, with a sign-in form that includes at least a username input, a password input, and a submit button. I used ChatGPT to generate a sample HTML, and here I come with this pinky lovely sign-in page.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqi1omhnc8j0rsypkhwg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqi1omhnc8j0rsypkhwg.png" alt="Image description" width="800" height="693"&gt;&lt;/a&gt;&lt;br&gt;
I have omitted the CSS styles for simplicity, and here is how simple the HTML looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8" /&amp;gt;
    &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&amp;gt;
    &amp;lt;title&amp;gt;Fun Sign-in Page&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Welcome Back&amp;lt;/h1&amp;gt;
      &amp;lt;form id="signInForm"&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;label for="username"&amp;gt;Username&amp;lt;/label&amp;gt;
          &amp;lt;input type="text" name="username" required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;label for="password"&amp;gt;Password&amp;lt;/label&amp;gt;
          &amp;lt;input type="password" name="password" required /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;button type="submit"&amp;gt;Sign in&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also start with a boilerplate from your favorite front-end framework, such as Create React App, Next.js, or Vue CLI.&lt;/p&gt;

&lt;p&gt;Since Logto is open source, another recommendation is to clone the &lt;a href="https://github.com/logto-io/logto/tree/master/packages/experience" rel="noopener noreferrer"&gt;Logto Experience project&lt;/a&gt;, and modify the code to fit your needs. This is the full feature Logto built-in sign-in experience UI, written in React and TypeScript.&lt;/p&gt;

&lt;h1&gt;
  
  
  Setup Logto tunnel CLI
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://docs.logto.io/docs/references/tunnel-cli/" rel="noopener noreferrer"&gt;Logto tunnel CLI&lt;/a&gt; is a tool that not only servers your HTML pages, but also allows you to interact with Logto's Experience API from your HTML pages in local dev environment.&lt;br&gt;
Assuming your &lt;code&gt;index.html&lt;/code&gt; page is located in &lt;code&gt;/path/to/your/custom-ui&lt;/code&gt;, and your Logto tenant ID is &lt;code&gt;foobar&lt;/code&gt;, you can run the command this way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx @logto/tunnel --path /path/to/your/custom-ui --endpoint https://foobar.logto.app/ -p 9000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you are using a UI framework that has a built-in development server, and your page is served at &lt;code&gt;http://localhost:4000&lt;/code&gt;, you can run the command like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx @logto/tunnel --uri http://localhost:4000 --endpoint https://foobar.logto.app/ -p 9000

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running the command, the tunnel service will be started on your local machine &lt;code&gt;http://localhost:9000/&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Trigger sign-in from your application
&lt;/h1&gt;

&lt;p&gt;Go to the application you created earlier, and change the Logto endpoint from &lt;code&gt;https://foobar.logto.app/&lt;/code&gt; to &lt;code&gt;http://localhost:9000/&lt;/code&gt; in your Logto SDK config.&lt;/p&gt;

&lt;p&gt;Let's take our React sample project as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { LogtoProvider, LogtoConfig } from '@logto/react';

const config: LogtoConfig = {
  // endpoint: 'https://foobar.logto.app/', // original Logto Cloud endpoint
  endpoint: 'http://localhost:9000/', // tunnel service address
  appId: '&amp;lt;your-application-id&amp;gt;',
};

const App = () =&amp;gt; (
  &amp;lt;LogtoProvider config={config}&amp;gt;
    &amp;lt;YourAppContent /&amp;gt;
  &amp;lt;/LogtoProvider&amp;gt;
);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click the "Sign in" button in your application. Instead of seeing the built-in Logto sign-in UI, you should now be redirected to your custom sign-in page.&lt;/p&gt;

&lt;h1&gt;
  
  
  Interact with Logto Experience API
&lt;/h1&gt;

&lt;p&gt;In this step, we will interact with Logto Experience API in your custom UI. First, let's create a &lt;code&gt;script.js&lt;/code&gt; file and add the reference in your &lt;code&gt;index.html&lt;/code&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;lt;html&amp;gt;
  &amp;lt;!-- Omitted code ... --&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;!-- Omitted code ... --&amp;gt;
    &amp;lt;script src="script.js"&amp;gt;&amp;lt;/script&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put the following code in your &lt;code&gt;script.js&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.onload = function () {
  const form = document.getElementById('loginForm');
  form.addEventListener('submit', async function (event) {
    event.preventDefault();
    const formData = new FormData(event.target);

    try {
      await fetch('/api/experience', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ interactionEvent: 'SignIn' }),
      });

      const verificationResponse = await fetch('/api/experience/verification/password', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          identifier: { type: 'username', value: formData.get('username') },
          password: formData.get('password'),
        }),
      });

      const { verificationId } = await verificationResponse.json();

      await fetch('/api/experience/identification', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ verificationId }),
      });

      const submitReponse = await fetch('/api/experience/submit', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
      });

      const { redirectTo } = await submitReponse.json();
      window.location.replace(redirectTo);

    } catch (error) {
      console.error('Oops!', error);
    }
  });
};

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script will complete the username and password sign-in flow with the help of these APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PUT /api/experience&lt;/code&gt; - Start the sign-in interaction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/experience/verification/password&lt;/code&gt; - Verify the username and password&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/experience/identification&lt;/code&gt; - Identify user for the current interaction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /api/experience/submit&lt;/code&gt; - Submit the sign-in interaction
Refer to &lt;a href="https://openapi.logto.io/group/endpoint-experience" rel="noopener noreferrer"&gt;Logto Experience API docs&lt;/a&gt; for more details.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Test your custom sign-in page
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;Go to your application and click the "Sign in" button.&lt;/li&gt;
&lt;li&gt;You should be redirected to your custom sign-in page at &lt;code&gt;http://localhost:9000/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Enter username and password, and click the "Submit" button.&lt;/li&gt;
&lt;li&gt;If everything is set up correctly, you should be redirected back to your application, with authenticated user information.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Deploy your custom sign-in UI to Logto Cloud
&lt;/h1&gt;

&lt;p&gt;Once you have finished developing and testing your custom sign-in UI locally, you can deploy it to Logto Cloud. Simply create a zip file of your custom UI folder and upload it to the "Custom UI" section in your Logto Console.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyhuh571brexp40ekfh1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyhuh571brexp40ekfh1.png" alt="Image description" width="800" height="385"&gt;&lt;/a&gt;&lt;br&gt;
After uploading, save the changes, and your custom sign-in UI will go live in your Logto Cloud tenant, replacing the built-in Logto sign-in experience.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qy3ttykd82kqznn8w61.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6qy3ttykd82kqznn8w61.png" alt="Image description" width="800" height="424"&gt;&lt;/a&gt;&lt;br&gt;
Finally, go back to your application and change the endpoint URI back to your Logto cloud endpoint: &lt;code&gt;https://foobar.logto.app/&lt;/code&gt;. This time, you can stop the Logto tunnel service, and your application should now work directly with the custom UI hosted online.&lt;/p&gt;
&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In this tutorial, we've walked you through the process of implementing and deploying your own custom sign-in UI to Logto Cloud.&lt;/p&gt;

&lt;p&gt;With this feature, you can now deeply customize your sign-in UI and auth flows to match your branding and specific business requirements.&lt;/p&gt;

&lt;p&gt;Happy coding! 🚀&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>identity</category>
      <category>opensource</category>
      <category>ui</category>
    </item>
    <item>
      <title>Color palette in branding: How Logto generate a custom color scheme for your brand</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Wed, 28 Aug 2024 01:05:15 +0000</pubDate>
      <link>https://forem.com/logto/color-palette-in-branding-how-logto-generate-a-custom-color-scheme-for-your-brand-404j</link>
      <guid>https://forem.com/logto/color-palette-in-branding-how-logto-generate-a-custom-color-scheme-for-your-brand-404j</guid>
      <description>&lt;p&gt;How audiences perceive a brand is strongly influenced by color psychology. By using a carefully crafted color palette, brand recognition can be enhanced, leaving a lasting impression. To achieve this, we've developed a system that generates harmonious color schemes from a single base color, utilizing the HSL color model.&lt;/p&gt;




&lt;p&gt;Color psychology plays a significant role in how audiences perceive a brand. A well-crafted color palette can enhance brand recognition and leave a lasting impression. To achieve this, we've developed a system that leverages the HSL color model to generate harmonious color themes from a single base color. In this post, we'll uncover the secrets behind our color generation process.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is the HSL color model?
&lt;/h1&gt;

&lt;p&gt;The HSL (Hue, Saturation, Lightness) color model is a widely used representation in digital design, particularly because of its intuitive approach to color manipulation. HSL separates the chromatic aspects of color into three distinct components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Hue: &lt;br&gt;
Hue refers to the type of color we see and is represented as a degree on a 360° circle. Each angle corresponds to a specific color on the color wheel—0° is red, 120° is green, 240° is blue, and so on. By adjusting the hue value, you can shift from one color to another, making it a powerful tool for generating complementary or analogous color schemes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Saturation: &lt;br&gt;
Saturation determines the intensity or purity of the color. It ranges from 0% to 100%, where 0% represents a completely desaturated color, essentially a shade of gray, and 100% represents the full, vibrant color. Adjusting the saturation allows designers to create both vivid and muted versions of the same hue, which is particularly useful for creating color hierarchies or emphasizing certain elements.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lightness:&lt;br&gt;
Lightness controls the brightness of the color, ranging from 0% (black) to 100% (white). At 50% lightness, the color is at its purest; as you move towards 0% or 100%, the color becomes darker or lighter, respectively. This is particularly useful in creating different shades and tints of a base color, which can be used to define visual depth and contrast within a design.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Why it is important to use the HSL color model?
&lt;/h1&gt;

&lt;p&gt;In the context of Logto, using the HSL model allows for flexible and dynamic color theme generation. When a customer inputs their branding color, HSL makes it easier to calculate related color families—variations in lightness and saturation of the base hue. This capability ensures that the generated theme remains consistent and harmonious, reinforcing the brand's identity while ensuring an optimal user experience. The HSL model's intuitive nature also allows for more granular control over color adjustments, making it a preferred choice for designers and developers alike.&lt;/p&gt;

&lt;h1&gt;
  
  
  The color palette in Logto
&lt;/h1&gt;

&lt;p&gt;Our color palette model is designed based on the HSL color space. Starting with a primary color and generate color families by adjusting the hue, saturation, and lightness values. This approach ensures that all colors in the palette are visually compatible and create a harmonious brand experience.&lt;/p&gt;

&lt;p&gt;Here's a example of the default color palette model we are using in the sign-in experience product:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11qoo8frlple5jcc24ry.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F11qoo8frlple5jcc24ry.png" alt="Image description" width="800" height="557"&gt;&lt;/a&gt;In the frontend code base, the essential color families are defined as CSS variables. For example, the primary color family is defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--color-brand-30: #3C00C2; // hsla(253, 88%, 48%, 1);
--color-brand-40: #5d34f2; // hsla(253, 88%, 58%, 1);
--color-brand-50: #7C5CFF; // hsla(253, 88%, 68%, 1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By referencing these variables in the CSS stylesheets, we can easily maintain a consistent visual style across the entire platform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--color-brand-primary: var(--color-brand-40);
--color-brand-hover: var(--color-brand-50);
--color-brand-active: var(--color-brand-30);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Custom branding color palette generation
&lt;/h1&gt;

&lt;p&gt;As mentioned earlier, developers may bring their own branding color to generate a custom branding color palette. To achieve this, we provide a simple color calculation unit that takes the base color and generates the corresponding color families.&lt;/p&gt;

&lt;p&gt;Under the hood, we use &lt;a href="https://github.com/qix-/color#readme" rel="noopener noreferrer"&gt;color.js&lt;/a&gt; to manage the color manipulation process. The color generation function takes the base color, calculates the corresponding HSL values, and generates the color families HEX values accordingly.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate the base color element:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const baseColor = new Color(primaryColor);

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Define the HSL based color calculation function:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const absoluteLighten = (baseColor: color, delta: number) =&amp;gt; {
  const hslArray = baseColor.hsl().round().array();

  return color([hslArray[0] ?? 0, hslArray[1] ?? 0, (hslArray[2] ?? 0) + delta], 'hsl');
};

export const absoluteDarken = (baseColor: color, delta: number) =&amp;gt; {
  const hslArray = baseColor.hsl().round().array();

  return color([hslArray[0] ?? 0, hslArray[1] ?? 0, (hslArray[2] ?? 0) - delta], 'hsl');
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Generate the color families:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const colorPalette = {

  ['--color-brand-hover']: absoluteLighten(baseColor, 10).hex(),
  ['--color-brand-active']: absoluteDarken(baseColor, 10).hex(),
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy, right? Repeating the above steps we can generate a custom color palette for any branding color. This approach ensures that the generated color palette remains consistent with the brand's identity while providing a visually appealing experience for users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>identity</category>
      <category>design</category>
    </item>
    <item>
      <title>Create a remark plugin to extract MDX reading time</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Wed, 28 Aug 2024 00:09:20 +0000</pubDate>
      <link>https://forem.com/logto/create-a-remark-plugin-to-extract-mdx-reading-time-5d2h</link>
      <guid>https://forem.com/logto/create-a-remark-plugin-to-extract-mdx-reading-time-5d2h</guid>
      <description>&lt;p&gt;A guide to create a remark plugin to make the reading time data available when importing MDX files as ES modules.&lt;/p&gt;




&lt;p&gt;Remark is a powerful markdown processor that can be used to create custom plugins to transform markdown content. When parsing markdown files with remark, the content is transformed into an abstract syntax tree (AST) that can be manipulated using plugins.&lt;/p&gt;

&lt;p&gt;For a better user experience, it's common to display the estimated reading time of an article. In this guide, we'll create a remark plugin to extract the reading time data from an MDX file and make it available when importing the MDX file as an ES module.&lt;/p&gt;

&lt;h1&gt;
  
  
  Get started
&lt;/h1&gt;

&lt;p&gt;Let's start by creating an MDX file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Hello, world!

This is an example MDX file.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assuming we are using Vite as the bundler, with the official &lt;code&gt;@mdx-js/rollup&lt;/code&gt; plugin to transform MDX files, thus we can import the MDX file as an ES module. The Vite configuration should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [
    {
      // The `enforce: 'pre'` is required to make the MDX plugin work
      enforce: 'pre',
      ...mdx({
        // ...configurations
      }),
    },
  ],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we import the MDX file as an ES module, the content will be an object with the &lt;code&gt;default&lt;/code&gt; property containing the compiled JSX. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const mdx = await import('./example.mdx');
console.log(mdx);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Will yield:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  // ...other properties if you have plugins to transform the MDX content
  default: [Function: MDXContent],

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have the output like this, we can are ready to create the remark plugin.&lt;/p&gt;

&lt;h1&gt;
  
  
  Create the remark plugin
&lt;/h1&gt;

&lt;p&gt;Let's check out what we need to do to achieve the goal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract MDX content to text for reading time calculation.&lt;/li&gt;
&lt;li&gt;Calculate the reading time.&lt;/li&gt;
&lt;li&gt;Attach the reading time data to the MDX content, make it available when importing the MDX file as an ES module.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Luckily, there are already libraries to help us with the reading time calculation and basic AST operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;reading-time&lt;/code&gt; to calculate the reading time.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mdast-util-to-string&lt;/code&gt; to convert the MDX AST to text.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;estree-util-value-to-estree&lt;/code&gt; to convert the reading time data to an ESTree node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are a TypeScript user, you may also need to install these packages for type definitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@types/mdast&lt;/code&gt; for MDX root node type definitions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unified&lt;/code&gt; for plugin type definitions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As long as we have the packages installed, we can start creating the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { type Root } from 'mdast';
import { toString } from 'mdast-util-to-string';
import getReadingTime from 'reading-time';
import { type Plugin } from 'unified';

// The first argument is the configuration, which is not needed in this case. You can update the
// type if you need to have a configuration.
export const remarkMdxReadingTime: Plugin&amp;lt;void[], Root&amp;gt; = function () {
  return (tree) =&amp;gt; {
    const text = toString(tree);
    const readingTime = getReadingTime(text);

    // TODO: Attach the reading time data to the MDX content
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, the plugin simply extracts the MDX content to text and calculates the reading time. Now we need to attach the reading time data to the MDX content, and it looks not that straightforward. But if we take a look at the other awesome libraries like &lt;a href="https://github.com/remcohaszing/remark-mdx-frontmatter/blob/6e33330fad5d252389a0aaa016bb9638b8d6f057/src/remark-mdx-frontmatter.ts#L60-L86" rel="noopener noreferrer"&gt;remark-mdx-frontmatter&lt;/a&gt;, we can find a way to do it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { valueToEstree } from 'estree-util-value-to-estree';
import { type Root } from 'mdast';
import { toString } from 'mdast-util-to-string';
import getReadingTime from 'reading-time';
import { type Plugin } from 'unified';

export const remarkMdxReadingTime: Plugin&amp;lt;void[], Root&amp;gt; = function () {
  return (tree) =&amp;gt; {
    const text = toString(tree);
    const readingTime = getReadingTime(text);

    tree.children.unshift({
      type: 'mdxjsEsm',
      value: '',
      data: {
        estree: {
          type: 'Program',
          sourceType: 'module',
          body: [
            {
              type: 'ExportNamedDeclaration',
              specifiers: [],
              declaration: {
                type: 'VariableDeclaration',
                kind: 'const',
                declarations: [
                  {
                    type: 'VariableDeclarator',
                    id: { type: 'Identifier', name: 'readingTime' },
                    init: valueToEstree(readingTime, { preserveReferences: true }),
                  },
                ],
              },
            },
          ],
        },
      },
    });
  };
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;type: 'mdxjsEsm'&lt;/code&gt; in the code above. This is a node type that is used to &lt;a href="https://github.com/syntax-tree/mdast-util-mdxjs-esm/tree/5b5f3ff854a2dad6907b52bba9f4332d88cf4bd1" rel="noopener noreferrer"&gt;serialize MDX ESM&lt;/a&gt;. The code above attaches the &lt;code&gt;reading time&lt;/code&gt; data using the name readingTime to the MDX content, which will yield the following output when importing the MDX file as an ES module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  default: [Function: MDXContent],
  readingTime: { text: '1 min read', minutes: 0.1, time: 6000, words: 2 }, // The reading time data

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to change the name of the reading time data, you can update the &lt;code&gt;name&lt;/code&gt; property of the &lt;code&gt;Identifier&lt;/code&gt; node.&lt;/p&gt;

&lt;h1&gt;
  
  
  TypeScript support
&lt;/h1&gt;

&lt;p&gt;To make the plugin even more developer-friendly, we can make one last touch by augmenting the MDX type definitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;declare module '*.mdx' {
  import { type ReadTimeResults } from 'reading-time';

  export const readingTime: ReadTimeResults;
  // ...other augmentations
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when importing the MDX file, TypeScript will recognize the &lt;code&gt;readingTime&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { readingTime } from './example.mdx';

console.log(readingTime); // { text: '1 min read', minutes: 0.1, time: 6000, words: 2 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I hope this guide helps you have a better experience when working with MDX files. With this remark plugin, you can use the reading time data directly and even leverage ESM tree-shaking for better performance.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>mdx</category>
      <category>opensource</category>
      <category>identity</category>
    </item>
    <item>
      <title>How does the browser process the URL input in the address bar?</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Tue, 20 Aug 2024 05:25:21 +0000</pubDate>
      <link>https://forem.com/logto/how-does-the-browser-process-the-url-input-in-the-address-bar-4mne</link>
      <guid>https://forem.com/logto/how-does-the-browser-process-the-url-input-in-the-address-bar-4mne</guid>
      <description>&lt;p&gt;When we open a particular URL in the browser, how does the browser load and display the content? We show what the browser did in turn, according to the order in which the event occurs.&lt;/p&gt;




&lt;p&gt;In addition to a few specific web browsing with custom native apps while browsing different web pages, most web pages are browsed with a browser. So when we open a particular URL in the browser, how does the browser load and display the content? We will show what the browser did in turn, according to the order in which the event occurs.&lt;/p&gt;

&lt;h1&gt;
  
  
  User inputs URL in browser
&lt;/h1&gt;

&lt;p&gt;In our previous &lt;a href="https://blog.logto.io/unveiling-uri-url-and-urn/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; we covered URL components including what is known as the host/domain, e.g. &lt;code&gt;www.google.com&lt;/code&gt; &lt;code&gt;blog.logto.io&lt;/code&gt; etc.&lt;/p&gt;

&lt;h1&gt;
  
  
  IP address lookup using host/domain
&lt;/h1&gt;

&lt;p&gt;Browser cannot directly understand the host/domain and find the corresponding resources, but needs to know the specific IP address to locate the location of the resources required for web pages.&lt;/p&gt;

&lt;p&gt;The browser will find the IP address corresponding to the host/domain through the domain name system (DNS).&lt;/p&gt;

&lt;p&gt;In order to make the process of searching for IP addresses as fast as possible, the correspondence between host/domain and IP address often uses various caches, such as browser cache, operating system cache, and so on.&lt;/p&gt;

&lt;p&gt;When cache lookup misses, we go through the regular DNS lookup process to find a host/domain's IP address. These are the steps:&lt;br&gt;
&lt;a href="https://media.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%2Fthxetkfdskkqh42yvh94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fthxetkfdskkqh42yvh94.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When a user enters &lt;code&gt;blog.logto.io&lt;/code&gt; in the web browser, the web browser requests the DNS service to get the IP address, the corresponding query is received by DNS resolver&lt;/li&gt;
&lt;li&gt;DNS resolver queries DNS root server (.)&lt;/li&gt;
&lt;li&gt;Root server returns an address corresponding to a Top Level Domain (TLD) DNS server (&lt;code&gt;.io&lt;/code&gt; in this case) and some associated information&lt;/li&gt;
&lt;li&gt;DNS resolver query &lt;code&gt;.io&lt;/code&gt; TLD server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.io&lt;/code&gt; TLD server respond with &lt;code&gt;logto.io&lt;/code&gt; name server address&lt;/li&gt;
&lt;li&gt;DNS resolver requests &lt;code&gt;logto.io&lt;/code&gt; domain name server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;logto.io&lt;/code&gt; domain name server respond with &lt;code&gt;blog.logto.io&lt;/code&gt; address&lt;/li&gt;
&lt;li&gt;DNS resolver forward &lt;code&gt;blog.logto.io&lt;/code&gt; IP address back to web browser&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The web browser can then request the corresponding resources from the server and render the website for the user to view.&lt;/p&gt;
&lt;h1&gt;
  
  
  Establish TCP connection
&lt;/h1&gt;

&lt;p&gt;After obtaining the IP address corresponding to the domain to be browsed through the DNS service, the web browser initiates and establishes a TCP connection with the server.&lt;/p&gt;
&lt;h1&gt;
  
  
  Get resources and render web pages
&lt;/h1&gt;

&lt;p&gt;After establishing a TCP connection, the browser initiates a network request to the server, obtains the corresponding resources, and renders the web page content based on the obtained resources for the user to browse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>identity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Everything you need to know about Base64</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Tue, 20 Aug 2024 05:17:24 +0000</pubDate>
      <link>https://forem.com/logto/everything-you-need-to-know-about-base64-3p9f</link>
      <guid>https://forem.com/logto/everything-you-need-to-know-about-base64-3p9f</guid>
      <description>&lt;p&gt;Dive deep into the world of Base64 encoding. Learn its history, how it works, when to use it, and its limitations. Essential knowledge for every developer dealing with data encoding and transmission.&lt;/p&gt;




&lt;p&gt;In the world of software development, Base64 is a concept often mentioned but not always fully understood. Whether you're a newcomer to the field or an experienced developer, a deep understanding of Base64 can help you handle data encoding and transmission with ease. Let's explore all aspects of Base64, from its definition and origins to practical applications and usage considerations.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Base64?
&lt;/h1&gt;

&lt;p&gt;Base64 is an encoding method that represents binary data using 64 printable characters. These 64 characters include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;A-Z&lt;/code&gt;, &lt;code&gt;a-z&lt;/code&gt;, &lt;code&gt;0-9&lt;/code&gt; (62 letters and numbers)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; (2 special characters)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;=&lt;/code&gt; (used for padding)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our daily development work, Base64 is ubiquitous. You may have encountered it in the following scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Embedding small images or icons in HTML&lt;/li&gt;
&lt;li&gt;Transmitting binary data in API responses&lt;/li&gt;
&lt;li&gt;Encoding email attachments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, you might have seen HTML code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;img
  src="data:image/png;base64,data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="
  alt="Red dot"
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The long string here is a small image encoded in Base64.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Base64?
&lt;/h1&gt;

&lt;p&gt;To understand the reason for Base64's existence, we need to look back at the early history of computer development.&lt;/p&gt;

&lt;p&gt;In the early days of computer networks, most systems could only handle printable ASCII characters. &lt;a href="https://en.wikipedia.org/wiki/ASCII" rel="noopener noreferrer"&gt;ASCII&lt;/a&gt; encoding uses only 7 bits of binary data, representing 128 characters. This works fine for handling English text, but problems arise when transmitting binary data (such as images or audio files).&lt;/p&gt;

&lt;p&gt;Different systems might interpret certain control characters differently, potentially corrupting data during transmission. For example, some systems might change line breaks from LF (Line Feed) to CR (Carriage Return) + LF, which would be disastrous for binary data.&lt;/p&gt;

&lt;p&gt;To solve this problem, people began looking for a way to convert arbitrary binary data into characters that could be safely transmitted. This is where Base64 encoding came from.&lt;/p&gt;

&lt;p&gt;In fact, before Base64, there were Base16 (using 16 characters) and Base32 (using 32 characters) encoding methods. However, Base64 struck the best balance between encoding efficiency and practicality, making it the most widely used encoding method.&lt;/p&gt;

&lt;h1&gt;
  
  
  How Base64 encoding works
&lt;/h1&gt;

&lt;p&gt;The core idea of Base64 is to encode 3 bytes (24 bits) of binary data into 4 printable characters.&lt;/p&gt;

&lt;p&gt;Let's understand this process through a concrete example.&lt;/p&gt;

&lt;p&gt;Suppose we want to encode the string "Logto":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we convert "Logto" to ASCII code:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;L&lt;/code&gt;: &lt;code&gt;76&lt;/code&gt; (&lt;code&gt;01001100&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;o&lt;/code&gt;: &lt;code&gt;111&lt;/code&gt; (&lt;code&gt;01101111&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;g&lt;/code&gt;: &lt;code&gt;103&lt;/code&gt; (&lt;code&gt;01100111&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;t&lt;/code&gt;: &lt;code&gt;116&lt;/code&gt; (&lt;code&gt;01110100&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;o&lt;/code&gt;: &lt;code&gt;111&lt;/code&gt; (&lt;code&gt;01101111&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;We concatenate these binary numbers (total of 5 bytes, 40 bits): &lt;code&gt;0100110001101111011001110111010001101111&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We divide these bits into groups of 6 bits (note that the last group only has 4 bits): &lt;code&gt;010011&lt;/code&gt; | &lt;code&gt;000110&lt;/code&gt; | &lt;code&gt;111101&lt;/code&gt; | &lt;code&gt;100111&lt;/code&gt; | &lt;code&gt;011101&lt;/code&gt; | &lt;code&gt;000110&lt;/code&gt; | &lt;code&gt;1111&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Since the last group only has 4 bits, we need to add two 0s at the end to make it 6 bits: &lt;code&gt;010011&lt;/code&gt; | &lt;code&gt;000110&lt;/code&gt; | &lt;code&gt;111101&lt;/code&gt; | &lt;code&gt;100111&lt;/code&gt; | &lt;code&gt;011101&lt;/code&gt; | &lt;code&gt;000110&lt;/code&gt; | &lt;code&gt;111100&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We convert each 6-bit group to decimal: &lt;code&gt;19&lt;/code&gt; | &lt;code&gt;6&lt;/code&gt; | &lt;code&gt;61&lt;/code&gt; | &lt;code&gt;39&lt;/code&gt; | &lt;code&gt;29&lt;/code&gt; | &lt;code&gt;6&lt;/code&gt; | &lt;code&gt;60&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;According to the &lt;a href="https://en.wikipedia.org/wiki/Base64#Base64_table_from_RFC_4648" rel="noopener noreferrer"&gt;Base64 encoding table&lt;/a&gt;, we convert these numbers to their corresponding characters: &lt;code&gt;T&lt;/code&gt; | &lt;code&gt;G&lt;/code&gt; | &lt;code&gt;9&lt;/code&gt; | &lt;code&gt;n&lt;/code&gt; | &lt;code&gt;d&lt;/code&gt; | &lt;code&gt;G&lt;/code&gt; | &lt;code&gt;8&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Finally, because Base64 encoding always encodes 3 bytes (24 bits) of binary data into 4 printable characters, and "Logto" converts to 5 bytes in binary, the first 3 bytes are encoded as &lt;code&gt;TG9n&lt;/code&gt;, and the last 2 bytes are encoded as &lt;code&gt;dG8&lt;/code&gt;. Therefore, we need to add one &lt;code&gt;=&lt;/code&gt; as a padding character at the end.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thus, the Base64 encoding result of "Logto" is &lt;code&gt;TG9ndG8=&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In Node.js, we can generate Base64 encoding like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const text = 'Logto';
const base64 = Buffer.from(text).toString('base64');
console.log(base64); // Output: TG9ndG8=
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example demonstrates several important features of Base64 encoding:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every 3 bytes of input produces 4 characters of output.&lt;/li&gt;
&lt;li&gt;When the number of input bytes is not a multiple of 3, padding characters "=" are used. In this example, we have 5 input bytes, which produces 7 Base64 characters and 1 padding character.&lt;/li&gt;
&lt;li&gt;The number of padding characters can tell us the exact number of bytes in the original data:

&lt;ul&gt;
&lt;li&gt;No padding: The original data is a multiple of 3 bytes&lt;/li&gt;
&lt;li&gt;1 &lt;code&gt;=&lt;/code&gt;: 2 zero bits were added to the original data before encoding&lt;/li&gt;
&lt;li&gt;2 &lt;code&gt;=&lt;/code&gt;: 4 zero bits were added to the original data before encoding&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  When and why to use Base64
&lt;/h1&gt;

&lt;p&gt;Base64 is particularly useful in the following scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Embedding small binary data (such as small images or icons) in HTML&lt;/li&gt;
&lt;li&gt;Transmitting binary data in protocols that can only transmit text&lt;/li&gt;
&lt;li&gt;Transmitting data in systems with restrictions on special characters&lt;/li&gt;
&lt;li&gt;Simple data obfuscation (Note: This is not encryption!)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The main advantages of using Base64 are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good cross-platform compatibility: Base64 encoded data can be correctly parsed in any system that supports ASCII&lt;/li&gt;
&lt;li&gt;Can improve transmission efficiency in some cases: For example, when the transmitted data contains a large number of repeating binary patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Besides standard Base64, there are some variants worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL-safe Base64: Replace + with -, / with _, and remove =. This encoding can be used directly in URLs without additional encoding.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const base64 = 'TG9ndG8=';
const urlSafe = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
console.log(urlSafe);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Limitations and considerations of Base64
&lt;/h1&gt;

&lt;p&gt;Although Base64 is useful, it also has some limitations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data inflation: Base64 encoding increases data volume by about 33%. For large amounts of data, this can lead to significant storage and bandwidth overhead.&lt;/li&gt;
&lt;li&gt;Performance impact: The encoding and decoding process requires CPU time. For large amounts of data or high-frequency operations, this can become a performance bottleneck.&lt;/li&gt;
&lt;li&gt;Security misconceptions: Many people mistakenly believe that Base64 is a form of encryption. In fact, Base64 is just encoding and can be easily decoded. Don't use it to protect sensitive information!&lt;/li&gt;
&lt;li&gt;Readability: Base64 encoded data is not human-readable. This can make debugging difficult.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When using Base64 in large applications, consider the following optimization strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only Base64 encode necessary data&lt;/li&gt;
&lt;li&gt;Consider using specialized Base64 encoding/decoding libraries, which are often more efficient than general-purpose libraries&lt;/li&gt;
&lt;li&gt;Perform Base64 encoding/decoding on the client side to reduce server load&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Base64 is a simple yet powerful tool that can solve many problems when used in the right scenarios. Understanding its working principle, applicable scenarios, and limitations can help you make smarter decisions in software development. I hope this article has helped you gain a comprehensive understanding of Base64, enabling you to handle related issues with ease.&lt;/p&gt;

&lt;p&gt;Remember, like all technical tools, the key is to use Base64 at the right time and in the right place. Wishing you all the best on your programming journey!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>opensource</category>
      <category>identity</category>
      <category>security</category>
    </item>
    <item>
      <title>From Parcel to Vite: A short story of a 100K LOC migration</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Tue, 06 Aug 2024 01:03:46 +0000</pubDate>
      <link>https://forem.com/logto/from-parcel-to-vite-a-short-story-of-a-100k-loc-migration-2m72</link>
      <guid>https://forem.com/logto/from-parcel-to-vite-a-short-story-of-a-100k-loc-migration-2m72</guid>
      <description>&lt;p&gt;We've migrated our three frontend projects from Parcel to Vite, and the process was... smooth.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  The backstory
&lt;/h2&gt;

&lt;p&gt;We have three main frontend projects at Logto: the sign-in experience, the Console, and the live preview. These projects are all in TypeScript, React, and SASS modules; in total, they have around 100K lines of code.&lt;/p&gt;

&lt;p&gt;We loved Parcel for its simplicity and zero-config setup. I can still remember the day when I was shocked by how easy it was to set up a new project with Parcel. You can just run &lt;code&gt;parcel index.html&lt;/code&gt; and boom, all necessary dependencies are installed and the project is running. If you are an "experienced" developer, you may feel the same way comparing it to the old days of setting up with Gulp and Webpack. Parcel is like a magic wand.&lt;br&gt;
&lt;strong&gt;The simplicity of Parcel was the main reason we stuck with it for so long, even though it could be moody sometimes. For example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parcel sometimes failed to bundle the project because it couldn't find some chunk files that were actually there.&lt;/li&gt;
&lt;li&gt;It needed some hacky configurations to make it work with our monorepo setup.&lt;/li&gt;
&lt;li&gt;It doesn't support MDX 3 natively, so we had to create a custom transformer for it.&lt;/li&gt;
&lt;li&gt;It doesn't support manual chunks (as of the time of writing, the manual chunks feature is still &lt;a href="https://parceljs.org/features/code-splitting/#manual-shared-bundles" rel="noopener noreferrer"&gt;in the experimental stage&lt;/a&gt;), which is okay for most circumstances, but sometimes you need it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;So why did we decide to migrate to something else?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We were stuck with Parcel 2.9.3, which was released in June 2023. Every time a new version was released after that, we tried to upgrade, but it always failed with build errors.&lt;/li&gt;
&lt;li&gt;The latest version of Parcel was 2.12.0, released in February 2024. Although it has nearly daily commits, no new release has been made since then.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Someone even opened a discussion to ask &lt;a href="https://github.com/parcel-bundler/parcel/discussions/9790" rel="noopener noreferrer"&gt;if Parcel is dead&lt;/a&gt;. The official answer is no, Parcel is still alive, but it's in a we-are-working-on-a-large-refactor-and-no-time-for-minor-releases state. To us, it's like a "duck death": The latest version we can use is from more than a year ago, and we don't know when the next version will be released. It looks like it's dead, it acts like it's dead, so it's dead to us.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl09rnnqzsm2sd7w9bkg5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl09rnnqzsm2sd7w9bkg5.png" alt="Image description" width="800" height="337"&gt;&lt;/a&gt;&lt;em&gt;Trust me, we tried.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Vite?
&lt;/h3&gt;

&lt;p&gt;We knew &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; from &lt;a href="https://vitest.dev/" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt;. Several months ago, we were tired of Jest's ESM support (in testing) and wanted to try something new. Vitest won our hearts with the native ESM support and the Jest compatibility. It has an amazing developer experience, and it's powered by Vite.&lt;/p&gt;
&lt;h2&gt;
  
  
  The status quo
&lt;/h2&gt;

&lt;p&gt;You may have different settings in your project, but usually you will find plugin replacements as the Vite ecosystem is blooming. Here are our setups at the moment of migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monorepo:&lt;/strong&gt; We use PNPM (v9) workspaces to manage our monorepo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Module:&lt;/strong&gt; We use ESM modules for all our projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript:&lt;/strong&gt; We use TypeScript (v5.5.3) for all our projects with path aliases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React:&lt;/strong&gt; We use React (v18.3.1) for all our frontend projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling:&lt;/strong&gt; We use SASS modules for styling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SVG:&lt;/strong&gt; We use SVGs as React components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MDX:&lt;/strong&gt; We have MDX with GitHub Flavored Markdown and Mermaid support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy loading:&lt;/strong&gt; We need to lazy load some of our pages and components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compression:&lt;/strong&gt; We produce compressed assets (gzip and brotli) for our production builds.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The migration
&lt;/h2&gt;

&lt;p&gt;We started the migration by creating a new Vite project and playing around with it to see how it works. The process was smooth and the real migration only took a few days.&lt;/p&gt;
&lt;h3&gt;
  
  
  Out-of-the-box support
&lt;/h3&gt;

&lt;p&gt;Vite has out-of-the-box support for monorepo, ESM, TypeScript, React, and SASS. We only needed to install the necessary plugins and configurations to make it work.&lt;/p&gt;
&lt;h3&gt;
  
  
  Path alias
&lt;/h3&gt;

&lt;p&gt;Vite has built-in support for path aliases, for example, in our &lt;code&gt;tsconfig.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We only needed to add the same resolution in our &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';

export default defineConfig({
  resolve: {
    alias: [{ find: /^@\//, replacement: '/src/' }],
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the replacement path should be an absolute path, while it is relative to the project root. Alternatively, you can use the &lt;a href="https://github.com/aleclarson/vite-tsconfig-paths" rel="noopener noreferrer"&gt;vite-tsconfig-paths&lt;/a&gt; plugin to read the path aliases from the &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  React Fast Refresh and HMR
&lt;/h3&gt;

&lt;p&gt;Although Vite has built-in support for HMR, it is required to install a plugin to enable React Fast Refresh. We used the &lt;a href="https://github.com/vitejs/vite-plugin-react" rel="noopener noreferrer"&gt;@vitejs/plugin-react&lt;/a&gt; plugin which is provided by the Vite team and has great support for React features like Fast Refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SVG as React component
&lt;/h3&gt;

&lt;p&gt;We use the &lt;a href="https://github.com/pd4d10/vite-plugin-svgr" rel="noopener noreferrer"&gt;vite-plugin-svgr&lt;/a&gt; plugin to convert SVGs to React components. It's as simple as adding the plugin to the Vite config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';
import svgr from 'vite-plugin-svgr';

export default defineConfig({
  plugins: [svgr()],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, we didn't specify on which condition the SVGs should be converted to React components, so all the imports were converted. The plugin offers a better default configuration: only convert the SVGs that are imported with the &lt;code&gt;.svg?react&lt;/code&gt; extension. We updated our imports accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  SASS modules
&lt;/h3&gt;

&lt;p&gt;Although Vite has built-in support for SASS modules, there's one thing we need to care about: how the class names are formatted. It may be troublesome for users and our integration tests if the class names are not formatted consistently. The one-line configuration in the &lt;code&gt;vite.config.ts&lt;/code&gt; can solve the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';

export default defineConfig({
  css: {
    modules: {
      generateScopedName: '[name]__[local]--[hash:base64:5]', // Replace with your own format
    },
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bt the way, Parcel and Vite have different flavors of importing SASS files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- import * as styles from './index.module.scss';
+ import styles from './index.module.scss';
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;* as&lt;/code&gt; syntax, however, works in Vite, but it will cause the loss of modularized class names when you use dynamic keys to access the &lt;code&gt;styles&lt;/code&gt; object. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const className = 'button';
// This will not work as expecteds
return &amp;lt;button className={styles[className]}&amp;gt;Click me&amp;lt;/button&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MDX support
&lt;/h3&gt;

&lt;p&gt;Since Vite leverages Rollup under the hood, we can use the official &lt;a href="https://mdxjs.com/packages/rollup/" rel="noopener noreferrer"&gt;@mdx-js/rollup&lt;/a&gt; plugin to support MDX as well as its plugins. The configuration looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';
import mdx from '@mdx-js/rollup';
import rehypeMdxCodeProps from 'rehype-mdx-code-props';
import remarkGfm from 'remark-gfm';

export default defineConfig({
  plugins: [
    {
      // The `enforce: 'pre'` is required to make the MDX plugin work
      enforce: 'pre',
      ...mdx({
        providerImportSource: '@mdx-js/react',
        remarkPlugins: [remarkGfm],
        rehypePlugins: [[rehypeMdxCodeProps, { tagName: 'code' }]],
      }),
    },
  ],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;remarkGfm&lt;/code&gt; plugin is used to support GitHub Flavored Markdown, and the &lt;code&gt;rehypeMdxCodeProps&lt;/code&gt; plugin is used to pass the props to the code blocks in the MDX files like what &lt;a href="https://docusaurus.io/docs/markdown-features/code-blocks#code-title" rel="noopener noreferrer"&gt;Docusaurus does&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mermiad support within MDX
&lt;/h3&gt;

&lt;p&gt;We would like to use Mermaid diagrams in our MDX files as other programming languages. The usage should be as simple as other code blocks:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eaxp2ec7s2s3h644r3n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2eaxp2ec7s2s3h644r3n.png" alt="Image description" width="800" height="214"&gt;&lt;/a&gt;Should be rendered as:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5txrva5x3301sqwronb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi5txrva5x3301sqwronb.png" alt="Image description" width="800" height="200"&gt;&lt;/a&gt;Since our app supports light and dark themes, we coded a little bit to make the Mermaid diagrams work with the dark theme. A React component is created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { useEffect } from 'react';
import useTheme from '@/hooks/use-theme';

// Maybe defined elsewhere
enum Theme {
  Dark = 'dark',
  Light = 'light',
}

type Props = {
  readonly children: string;
};

const themeToMermaidTheme = Object.freeze({


} satisfies Record&amp;lt;Theme, string&amp;gt;);

export default function Mermaid({ children }: Props) {
  const theme = useTheme();

  useEffect(() =&amp;gt; {
    (async () =&amp;gt; {
      const { default: mermaid } = await import('mermaid');

      mermaid.initialize({
        startOnLoad: false,
        theme: themeToMermaidTheme[theme],
        securityLevel: 'loose',
      });
      await mermaid.run();
    })();
  }, [theme]);

  return &amp;lt;div className="mermaid"&amp;gt;{children}&amp;lt;/div&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;useTheme&lt;/code&gt; is a custom hook to get the current theme from the context. The &lt;code&gt;mermaid&lt;/code&gt; library is imported asynchronously to reduce the loading size for the initial page load.&lt;/p&gt;

&lt;p&gt;For the code block in the MDX file, we have a unified component to do the job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import CodeEditor from '@/components/CodeEditor';
import Mermaid from '@/components/Mermaid';

type Props = {
  readonly children: string;
  readonly className?: string;
};

export default function Code({ children, className }: Props) {
  const [, language] = /language-(\w+)/.exec(String(className ?? '')) ?? [];

  if (language === 'mermaid') {
    return &amp;lt;Mermaid&amp;gt;{String(children).trimEnd()}&amp;lt;/Mermaid&amp;gt;;
  }

  // Other code blocks go here
  return &amp;lt;CodeEditor&amp;gt;{String(children).trimEnd()}&amp;lt;/CodeEditor&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we define the MDX provider as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { MDXProvider } from '@mdx-js/react';
import { ReactNode } from 'react';

type Props = {
  readonly children: ReactNode;
};

export default function MdxProvider({ children }: Props) {
  return (
    &amp;lt;MDXProvider
      components={{
        code: Code,
        // Explicitly set a `Code` component since `&amp;lt;code /&amp;gt;` cannot be swapped out with a
        // custom component now.
        // See: https://github.com/orgs/mdx-js/discussions/2231#discussioncomment-4729474
        Code,
        // ...other components
      }}
    &amp;gt;
      {children}
    &amp;lt;/MDXProvider&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Lazy loading
&lt;/h3&gt;

&lt;p&gt;This isn't a Vite-specific thing, it's still worth mentioning since we updated our pages to use lazy loading during the migration, and nothing broke afterward.&lt;/p&gt;

&lt;p&gt;React has a built-in &lt;code&gt;React.lazy&lt;/code&gt; function to lazy load components. However, it may cause some issues when you are iterating fast. We crafted a tiny library called &lt;a href="https://github.com/silverhand-io/react-safe-lazy" rel="noopener noreferrer"&gt;react-safe-lazy&lt;/a&gt; to solve the issues. It's a drop-in replacement for &lt;code&gt;React.lazy&lt;/code&gt; and a detailed explanation can be found in this &lt;a href="https://blog.logto.io/react-safe-lazy/?ref=post-vite" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compression
&lt;/h3&gt;

&lt;p&gt;There's a neat plugin called &lt;a href="https://github.com/vbenjs/vite-plugin-compression" rel="noopener noreferrer"&gt;vite-plugin-compression&lt;/a&gt; to produce compressed assets. It supports both gzip and brotli compression. The configuration is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';
import viteCompression from 'vite-plugin-compression';

export default defineConfig({
  plugins: [
    // Gzip by default
    viteCompression(),
    // Brotli
    viteCompression({ algorithm: 'brotliCompress' }),
  ],
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manual chunks
&lt;/h3&gt;

&lt;p&gt;One great feature of Vite (or the underlying Rollup) is the manual chunks. While &lt;code&gt;React.lazy&lt;/code&gt; is used for lazy loading components, we can have more control over the chunks by specifying the manual chunks to decide which components or modules should be bundled together.&lt;/p&gt;

&lt;p&gt;For example, we can first use &lt;a href="https://github.com/KusStar/vite-bundle-visualizer" rel="noopener noreferrer"&gt;vite-bundle-visualizer&lt;/a&gt; to analyze the bundle size and dependencies. Then we can write a proper function to split the chunks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    rollupOptions: {
      manualChunks(id, { getModuleInfo }) {
        // Dynamically check if the module has React-related dependencies
        const hasReactDependency = (id: string): boolean =&amp;gt; {
          return (
            getModuleInfo(id)?.importedIds.some(
              (importedId) =&amp;gt; importedId.includes('react') || importedId.includes('react-dom')
            ) ?? false
          );
        };

        // Caution: React-related packages should be bundled together otherwise it may cause runtime errors
        if (id.includes('/node_modules/') &amp;amp;&amp;amp; hasReactDependency(id)) {
          return 'react';
        }

        // Add your large packages to the list
        for (const largePackage of ['mermaid', 'elkjs']) {
          if (id.includes(`/node_modules/${largePackage}/`)) {
            return largePackage;
          }
        }

        // All other packages in the `node_modules` folder will be bundled together
        if (id.includes('/node_modules/')) {
          return 'vendors';
        }

        // Use the default behavior for other modules
      },
    },
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dev server
&lt;/h3&gt;

&lt;p&gt;Unlike the production build, Vite will NOT bundle your source code in the dev mode (including linked dependencies in the same monorepo) and treat every module as a file. For us, the browser will load hundreds of modules for the first time, which looks crazy but it's actually fine in most cases. You can see the discussion &lt;a href="https://github.com/vitejs/vite/discussions/4803" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If it's a thing for you, an alternative-but-not-perfect solution is to list the linked dependencies in the &lt;code&gt;optimizeDeps&lt;/code&gt; option of the &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';

export default defineConfig({
  optimizeDeps: {
    include: ['@logto/phrases', '@logto/schemas'],
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will "pre-bundle" the linked dependencies and make the dev server faster. The pitfall is that HMR may not work as expected for the linked dependencies.&lt;/p&gt;

&lt;p&gt;Additionally, we use a proxy which serves the static files in production and proxies the requests to the Vitest server in development. We have some specific ports configured to avoid conflicts, and it's also easy to set up in the &lt;code&gt;vite.config.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    port: 3000,
    hmr: {
      port: 3001,
    },
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Environment variables
&lt;/h3&gt;

&lt;p&gt;Unlike Parcel, Vite uses a modern approach to handle environment variables by using &lt;code&gt;import.meta.env.&lt;/code&gt; It will automatically load the &lt;code&gt;.env&lt;/code&gt; files and replace the variables in the code. However, it requires all the environment variables to be prefixed with &lt;code&gt;VITE_&lt;/code&gt; (configurable).&lt;/p&gt;

&lt;p&gt;While we were using Parcel, it simply replaced the &lt;code&gt;process.env&lt;/code&gt; variables without checking the prefix. So we have come up with a workaround using the &lt;code&gt;define&lt;/code&gt; field to make the migration easier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';

export default defineConfig({
  define: {
    'import.meta.env.API_URL': JSON.stringify(process.env.API_URL),
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to gradually add the prefix to the environment variables and remove the &lt;code&gt;define&lt;/code&gt; field.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conslusion
&lt;/h1&gt;

&lt;p&gt;That's it! We've successfully migrated our three frontend projects from Parcel to Vite, and hope this short story can help you with your migration. Here's what the configuration looks like in the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import svgr from 'vite-plugin-svgr';
import mdx from '@mdx-js/rollup';
import rehypeMdxCodeProps from 'rehype-mdx-code-props';
import remarkGfm from 'remark-gfm';
import viteCompression from 'vite-plugin-compression';

export default defineConfig({
  resolve: {
    alias: [{ find: /^@\//, replacement: '/src/' }],
  },
  server: {
    port: 3000,
    hmr: {
      port: 3001,
    },
  },
  plugins: [
    react(),
    svgr(),
    {
      enforce: 'pre',
      ...mdx({
        providerImportSource: '@mdx-js/react',
        remarkPlugins: [remarkGfm],
        rehypePlugins: [[rehypeMdxCodeProps, { tagName: 'code' }]],
      }),
    },
    viteCompression(),
    viteCompression({ algorithm: 'brotliCompress' }),
  ],
  css: {
    modules: {
      generateScopedName: '[name]__[local]--[hash:base64:5]',
    },
  },
  build: {
    rollupOptions: {
      manualChunks(id, { getModuleInfo }) {
        // Manual chunks logic
      },
    },
  },
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>vite</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Is magic link sign-in dying? A closer look at its declining popularity</title>
      <dc:creator>Palomino</dc:creator>
      <pubDate>Mon, 05 Aug 2024 23:40:07 +0000</pubDate>
      <link>https://forem.com/logto/is-magic-link-sign-in-dying-a-closer-look-at-its-declining-popularity-1hlm</link>
      <guid>https://forem.com/logto/is-magic-link-sign-in-dying-a-closer-look-at-its-declining-popularity-1hlm</guid>
      <description>&lt;p&gt;Magic link sign-in is a convenient and secure way to authenticate users. However, its popularity has been declining in recent years. Let's explore the reasons behind this trend and discuss the future of magic link sign-in.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;Magic link sign-in has long been hailed as a secure and user-friendly way to authenticate users without passwords. By sending a unique link to the user's email address, magic link sign-in eliminates the need for users to remember complex passwords and provides an additional layer of security.&lt;/p&gt;

&lt;p&gt;However, in recent years, the popularity of magic link sign-in has been on the decline. Many products that once offered magic link sign-in as an authentication option have switched to other methods, such as social sign-in or biometric authentication (fingerprint or face recognition), and one-time passwords (OTP).&lt;/p&gt;

&lt;p&gt;Couple days ago, I saw someone tweeted that "Magic links are the worst log-in experience ever." And many people agreed and shared their own frustrated experiences with magic link sign-in.&lt;/p&gt;

&lt;p&gt;So I began to wonder: why is magic link sign-in falling out of favor?&lt;/p&gt;

&lt;h1&gt;
  
  
  What is magic link sign-in?
&lt;/h1&gt;

&lt;p&gt;For those unfamiliar with magic link, here's a brief overview of how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user enters their email address on the sign-in page.&lt;/li&gt;
&lt;li&gt;The server generates a unique token and sends it to the user's email address.&lt;/li&gt;
&lt;li&gt;The user clicks on the link in the email, which contains the token.&lt;/li&gt;
&lt;li&gt;A browser window opens, and the user is automatically logged in.&lt;/li&gt;
&lt;li&gt;For mobile app users, there will be a button on the mobile browser to help navigate them back to the mobile app.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Why people like magic link?
&lt;/h1&gt;

&lt;p&gt;Magic link sign-in offers several advantages over traditional password-based authentication methods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Easy to use:&lt;/strong&gt; No need to remember complex passwords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure:&lt;/strong&gt; Reduces the risk of password-related hacks like phishing or brute-force attacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple:&lt;/strong&gt; Good for people who struggling with technology and password management.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It used to be a popular choice for companies looking to provide a seamless and secure authentication experience for their users. But why are there complaints about it now?&lt;/p&gt;

&lt;h1&gt;
  
  
  The hassle of switching apps
&lt;/h1&gt;

&lt;p&gt;One big reason people don't like magic link any more is that it makes you switch between apps. Imagine you're on a mobile app and you want to sign in. After inputting your email, you will have to switch between the app, your mail app, and the browser, and finally if everything goes well then thank god you're back to the app.&lt;/p&gt;

&lt;p&gt;This can be annoying and time-consuming, especially if you're in a hurry or have a slow internet connection. Not to mention sometimes the email just doesn't arrive, or it goes to the spam folder. Those extra steps can easily frustrate users and make them less likely to choose this sign-in method or even use the app.&lt;/p&gt;

&lt;h1&gt;
  
  
  The rise of other passwordless sign-in methods
&lt;/h1&gt;

&lt;p&gt;Another reason for the decline in magic link sign-in popularity is the rise of other passwordless authentication methods. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Biometric authentication has become increasingly popular due to its convenience and security.&lt;/li&gt;
&lt;li&gt;Social sign-in that allows users to sign in using their existing social media accounts, is another popular alternative to traditional password-based authentication.&lt;/li&gt;
&lt;li&gt;OTP, which sends a one-time code to the user's phone or email, is also a widely used method, especially when mobile phones can autofill the OTP code for you now.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Password managers and autofill
&lt;/h1&gt;

&lt;p&gt;Password managers, such as 1password, web browsers, etc., have also played a role in the decline of magic link sign-in.&lt;/p&gt;

&lt;p&gt;With these password managers, users can securely store and autofill their passwords across different websites and apps, eliminating the need to remember or manually enter passwords. Especially when combined with biometric authentication, the overall user experience is more and more seamless.&lt;/p&gt;

&lt;h1&gt;
  
  
  The verdict: Is magic link sign-in dying?
&lt;/h1&gt;

&lt;p&gt;While magic link sign-in is still used by many companies, it's definitely facing tough competition and may struggle to stay relevant in the fast-changing world of online security. Do you still remember last time you used magic link to sign in? I honestly don't. So for me and many others, it seems it's indeed dying.&lt;/p&gt;

&lt;p&gt;What is your take on this? What is your favorite sign-in method and why? Please feel free to share your thoughts with us.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://logto.io/?ref=dev" class="ltag_cta ltag_cta--branded" rel="noopener noreferrer"&gt;Try Logto Cloud for free&lt;/a&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>identity</category>
      <category>opensource</category>
      <category>security</category>
    </item>
  </channel>
</rss>
