<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
xmlns:content="http://purl.org/rss/1.0/modules/content/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:sy="http://purl.org/rss/1.0/modules/syndication/">
	<channel>
		<title>Author at Infinum</title>
		<atom:link href="https://infinum.com/blog/author/tomislav-habalija/feed/" rel="self" type="application/rss+xml" />
		<link></link>
		<description>Building digital products</description>
		<lastBuildDate>Wed, 08 Apr 2026 14:17:14 +0000</lastBuildDate>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>

					<item>
				<image>
					<url>19254654https://infinum.com/uploads/2024/05/A_Guide_to_Password-Less_Login_Methods-pt.2-blogpost-hero-ig-min.webp</url>
				</image>
				<title>Secure Web Authentication  – Passkeys &#038; Web Authentication API</title>
				<link>https://infinum.com/blog/secure-web-authentication-passkeys-webauthn/</link>
				<pubDate>Mon, 27 May 2024 16:39:59 +0000</pubDate>
				<dc:creator>Tomislav Habalija</dc:creator>
				<guid isPermaLink="false">https://infinum.com/?p=19254654</guid>
				<description>
					<![CDATA[<p>Discover a user-friendly and secure web authentication method. Implement passkeys using the Web Authentication API for a seamless login experience.</p>
<p>The post <a href="https://infinum.com/blog/secure-web-authentication-passkeys-webauthn/">Secure Web Authentication  – Passkeys &amp; Web Authentication API</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-168"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-92">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-95"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-93">
	<p	class='typography typography--size-36-text js-typography block-paragraph__paragraph'
	data-id='es-94'
	>
	<strong>If you’re looking for a user-friendly and secure web authentication method, passkeys are the way to go. Learn how to implement two passkey flows using the Web Authentication API and explore the best practices for the process.</strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-98"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-96">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-97'
	>
	When it comes to authentication on the web, passwords are far from the best option, both in terms of security and user experience. Thankfully, there are also a number of passwordless options, like magic links and one-time passwords, and the one that is the focus of this article – passkeys.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-101"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-99">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-100'
	>
	In a previous article, we’ve provided <a href="https://infinum.com/blog/passwordless-authentication-methods/" target="_blank" rel="noreferrer noopener">a theoretical overview of passwordless authentication methods</a>. This time, we will dive deeper into the code and explain how to implement two passkey authentication flows—the user registration flow and the login flow—by using the Web Authentication API specification.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-104"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-102">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-103'
	>
	Passkeys – the method of choice for secure web authentication</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-107"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-105">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-106'
	>
	Before we get into the Web Authentication API, it’s important to explain how passkeys work. A passkey is a credential, a public-private key pair generated by an authenticator, used to access the application. The private key is stored securely on the user’s device, while the public key is sent to the server.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-110"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-108">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-109'
	>
	Most people already own and use the technology required for the passkey authentication process. For example, we could use a fingerprint reader or a facial recognition system like Apple’s Face ID or Android facial recognition. However, biometric authentication isn&#8217;t the only option for using passkeys. There are also software-based solutions like a browser profile, such as a Google Chrome browser profile.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-112"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-111'
	>
	With passkeys, the server does not need to know who the user is specifically, just that they are the same person who created the account, which is verified on the platform itself. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-115"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-113">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-114'
	>
	This means that the fingerprint used for verification never leaves the platform, and it is not shared anywhere.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-118"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-116">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-117'
	>
	With passkeys, the server does not need to know who the user is specifically, just that they are the same person who created the account, which is verified on the platform itself. This means that the fingerprint used for verification never leaves the platform, and it is not shared anywhere.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-121"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-119">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-120'
	>
	A convenient feature is that passkeys can be synced across devices if you are using a large ecosystem like Apple’s (iCloud Keychain) or Google’s (Google Password Manager).</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-124"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-122">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-123'
	>
	Web Authentication API </h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-127"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-125">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-126'
	>
	WebAuthn (short for <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API" target="_blank" rel="noreferrer noopener">Web Authentication API</a>) is an extension of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Credential_Management_API" target="_blank" rel="noreferrer noopener">Credential Management API</a>. It is an API specification that enables applications to use strong and secure authentication methods and secure multi-factor authentication (MFA) without the need for verification via a text message. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-130"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-128">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-129'
	>
	The WebAuthn API allows the end users to authenticate their identity using hardware or software-based authenticators, such as USB security keys or platform authenticators like fingerprint readers, instead of relying solely on passwords.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-133"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-131">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-132'
	>
	To provide secure registration and authentication of accounts, these authenticators rely on public-key cryptography. The user has to associate an authenticator with their account, which generates a public-private key pair.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-136"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-134">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-135'
	>
	This authentication method has a number of benefits, including:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-140"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-137">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<h3	class='typography typography--size-20-text js-typography bullet__heading'
	data-id='es-138'
	>
	<strong>Phishing protection</strong></h3><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-139'
	>
	An attacker who creates a phishing website with a fake login can’t log in as the user because the signature changes with the website’s origin.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-144"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-141">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<h3	class='typography typography--size-20-text js-typography bullet__heading'
	data-id='es-142'
	>
	<strong>Reduced impact of data breaches</strong></h3><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-143'
	>
	The only information an attacker can get from a data breach is the user’s public key, rendering the data worthless to the attacker.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-148"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-145">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<h3	class='typography typography--size-20-text js-typography bullet__heading'
	data-id='es-146'
	>
	<strong>Invulnerable to password attacks</strong></h3><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-147'
	>
	As the passkey is unique to the service, if the attacker obtains one service’s passkey, it is useless for another website.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-151"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-149">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-150'
	>
	The Web Authentication API is only available in secure contexts, meaning that the web application must use HTTPS. Using HTTPS means that the connection between the browser and the server is encrypted and secured using digital certificates. This is an industry standard and common practice nowadays.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-154"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-152">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-153'
	>
	How to implement Web Authentication API – deep dive into the code</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-157"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-155">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-156'
	>
	The WebAuthn API uses two methods. To register new user credentials, we use the <code>create</code> method, while the <code>get</code> method is used to retrieve the credentials we created.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-160"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-158">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-159'
	>
	In line with this, the registration flow creates the user account with the credentials provided, and the login flow validates them.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-163"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-161">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-162'
	>
	Registration flow</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-166"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-164">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-165'
	>
	To create a set of user credentials, we call the <code>create</code> method on the <code>navigator.credentials</code> object. The method requires a set of options, and one of them is the <code>publicKey</code>. Note that the <code>credentials</code> objects could also be created with the password option. This is usually used to implement automatic login into a web application.</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-171"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-169"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-170">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2024/05/Login_Methods_A-1400x1358.webp				media='(max-width: 699px)'
				type=image/webp								height="1358"
												width="1400"
				 />
												<img
					src="https://infinum.com/uploads/2024/05/Login_Methods_A.webp"
					class="image__img block-media__image-img"
					alt=""
										height="2328"
															width="2400"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-241"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-172">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-175"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-173">
	<h4	class='typography typography--size-24-text js-typography block-heading__heading'
	data-id='es-174'
	>
	<strong>Step 1: Triggering the registration process</strong></h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-178"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-176">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-177'
	>
	In the web application, the user enters a unique identifier (e.g. username, email, or phone number) that will be used for the account. The application then sends the identifier to the backend.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-181"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-179">
	<h4	class='typography typography--size-24-text js-typography block-heading__heading'
	data-id='es-180'
	>
	<strong><strong>Step 2: Validating the identifier and creating a challenge</strong></strong></h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-184"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-182">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-183'
	>
	The server checks if the account with the provided identifier already exists, and if yes, it stops the flow by returning an error response to the application. If an account with the provided identifier does not exist, it generates a random set of bytes called the <code>challenge</code>. This challenge is used by the browser to sign the credentials. This is how the server makes sure that the credentials haven’t been tampered with. The server can now create a temporary account with the new unique user ID and the challenge and return both properties to the application.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-187"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-185">
	<h4	class='typography typography--size-24-text js-typography block-heading__heading'
	data-id='es-186'
	>
	<strong><strong><strong>Step 3: Triggering credentials creation</strong></strong></strong></h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-190"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-188">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-189'
	>
	The application receives the response from the server and triggers the navigator credentials creation process. This will trigger a browser flow for creating a passkey, and depending on the options passed to the create method, different options could be presented to the user.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-193"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-191">
	<h4	class='typography typography--size-24-text js-typography block-heading__heading'
	data-id='es-192'
	>
	<strong><strong><strong><strong>Step 4: Verification of credentials</strong></strong></strong></strong></h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-196"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-194">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-195'
	>
	The user creates the passkey, and the application sends the credentials to the server. The server now needs to verify them.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-199"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-197">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-198'
	>
	After the last step, the account for the user can be created, along with the user session. Let&#8217;s dissect the code to uncover all the details:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-201"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">publicKeyCredentialCreationOptions</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">challenge</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">Uint8Array</span><span class="token">.</span><span class="token" style="color: #6f42c1;">from</span><span class="token">(</span><span class="token" style="color: #24292e;">challengeStringFromServer</span><span class="token">,</span><span class="token"> </span><span class="token">c</span><span class="token" style="color: #e36209;"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token" style="color: #24292e;">c</span><span class="token">.</span><span class="token" style="color: #6f42c1;">charCodeAt</span><span class="token">(</span><span class="token" style="color: #005cc5;">0</span><span class="token">)</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">pubKeyCredParams</span><span class="token">:</span><span class="token"> </span><span class="token">[</span><span class="token">{</span><span class="token">alg</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #005cc5;">7</span><span class="token">,</span><span class="token"> </span><span class="token">type</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">public-key</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">}</span><span class="token">]</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">rp</span><span class="token">:</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">name</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Your application name</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">id</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">yourapplicationdomain.com</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">user</span><span class="token">:</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">id</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">userIdFromServer</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">displayName</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">User name</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">name</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">user@email.com</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">authenticatorSelection</span><span class="token">:</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">authenticatorAttachment</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">cross-platform</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">userVerification</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">required</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">timeout</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #005cc5;">60000</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">credential</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">await</span><span class="token"> </span><span class="token" style="color: #24292e;">navigator</span><span class="token">.</span><span class="token" style="color: #24292e;">credentials</span><span class="token">.</span><span class="token" style="color: #6f42c1;">create</span><span class="token">(</span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">publicKey</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">publicKeyCredentialCreationOptions</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-204"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-202">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-203'
	>
	The <code>publicKeyCredentialCreationOptions</code> contain a set of options that will determine what will be displayed in the passkey creation UI, and describe how the passkey will be created. For example, <code>rp</code><strong> </strong>(short for relying party) is used to describe the origin responsible for registering and authenticating the user. This prevents phishing attacks, as it ties the passkey to the origin.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-207"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-205">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-206'
	>
	The important part of the options is the <code>challenge</code>. The challenge is a string that comes from the server, converted to an array of 8-bit unsigned integers. Later in the process, the server will have to verify the challenge to make sure that the public key was not tampered with.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-210"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-208">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-209'
	>
	The <code>authenticatorSelection</code> is used for describing which types of authenticators are acceptable. For example, by allowing only <code>platform</code> authenticators, the account will be tied exclusively to the device the user is currently using to browse the application. One of the options that could be set is <code>userVerification</code>, which will force the user to verify their identity if set to <code>required</code>. This discourages passkey creation with a user browser profile, for example, and requires some type of verification like a fingerprint or face ID.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-213"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-211">
	<h4	class='typography typography--size-30-text js-typography block-heading__heading'
	data-id='es-212'
	>
	Validating the registration data</h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-216"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-214">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-215'
	>
	The response from the <code>navigator.credentials.create</code> is a <code>Promise</code> of <code>CredentialType</code>, or in our case, a <code>PublicKeyCredential</code>, and it looks like this:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-218"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #24292e;">PublicKeyCredential</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">authenticatorAttach</span><span class="token" style="color: #6f42c1;">m</span><span class="token" style="color: #6f42c1;">ent</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">platform</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">i</span><span class="token" style="color: #6f42c1;">d</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">hJLU5F7Yyrzwnwj4bHmx1iMos4E</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">r</span><span class="token" style="color: #6f42c1;">a</span><span class="token" style="color: #6f42c1;">wId</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ArrayBuffer</span><span class="token">(</span><span class="token" style="color: #005cc5;">20</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">resp</span><span class="token" style="color: #6f42c1;">o</span><span class="token" style="color: #6f42c1;">nse</span><span class="token">:</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #6f42c1;">client</span><span class="token" style="color: #6f42c1;">D</span><span class="token" style="color: #6f42c1;">ataJSON</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ArrayBuffer</span><span class="token">(</span><span class="token" style="color: #005cc5;">121</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #6f42c1;">attestati</span><span class="token" style="color: #6f42c1;">o</span><span class="token" style="color: #6f42c1;">nObject</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ArrayBuffer</span><span class="token">(</span><span class="token" style="color: #005cc5;">306</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">t</span><span class="token" style="color: #6f42c1;">ype</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">public-key</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-221"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-219">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-220'
	>
	You should validate that the <code>challenge</code> is the same as provided in the step before, which makes sure that the credentials were not tampered with. You also need to validate that the <code>origin</code> value contains the expected value and that the RP ID matches your website to prevent phishing attacks.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-224"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-222">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-223'
	>
	The public key is located in the <code>attestationObject</code> in the <code>authData</code>, along with the metadata of the registration event. If you require user verification, ensure that the user verification flag is set to <code>true</code>. Check that the user presence flag is set to <code>true</code> since user presence is always required for passkeys.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-227"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-225">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-226'
	>
	Then, ensure that the credential ID hasn’t been registered for any user.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-230"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-228">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-229'
	>
	Verify that the algorithm used by the passkey provider to create the credential is the algorithm you listed. This ensures that users can only register with the algorithms you have chosen to allow.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-233"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-231">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-232'
	>
	After the validation is completed, the server should create an account and extract and store the <code>userId</code>, <code>publicKey,</code> and <code>credentialId</code>. The last step in the sequence is for the server to create a session for the user and allow further access to the application.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-236"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-234">
	<h4	class='typography typography--size-30-text js-typography block-heading__heading'
	data-id='es-235'
	>
	Login flow</h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-239"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-237">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-238'
	>
	The login process can also be split into a sequence of steps.</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-244"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-242"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-243">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2024/05/Login_Methods_B-1400x1358.webp				media='(max-width: 699px)'
				type=image/webp								height="1358"
												width="1400"
				 />
												<img
					src="https://infinum.com/uploads/2024/05/Login_Methods_B.webp"
					class="image__img block-media__image-img"
					alt=""
										height="2328"
															width="2400"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-328"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-245">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-248"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-246">
	<h4	class='typography typography--size-24-text js-typography block-heading__heading'
	data-id='es-247'
	>
	<strong>Step 1: Triggering the login process</strong></h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-251"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-249">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-250'
	>
	In the web application, the user enters a unique identifier (e.g. username, email, or phone number) that is used for the account. The application then sends the identifier to the backend.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-254"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-252">
	<h4	class='typography typography--size-24-text js-typography block-heading__heading'
	data-id='es-253'
	>
	<strong><strong>Step 2: Validating the identifier and creating a challenge</strong></strong></h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-257"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-255">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-256'
	>
	The server checks if the account with the provided identifier already exists. If not, it stops the flow by returning an error response to the application. If an account with the provided identifier does exist, it again generates a challenge. This challenge will be used to verify the application login response in the later stage.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-260"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-258">
	<h4	class='typography typography--size-24-text js-typography block-heading__heading'
	data-id='es-259'
	>
	<strong><strong><strong>Step 3: Triggering credentials verification</strong></strong></strong></h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-263"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-261">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-262'
	>
	The application receives the response from the server and triggers the navigator credentials get method. This will trigger a browser flow for retrieving the passkey.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-266"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-264">
	<h4	class='typography typography--size-24-text js-typography block-heading__heading'
	data-id='es-265'
	>
	<strong>Step 4: Verification of credentials</strong></h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-269"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-267">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-268'
	>
	The user provides the passkey, and the application sends the credentials to the server. The server then needs to compare the credentials against those stored upon registration.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-272"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-270">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-271'
	>
	After the last step, if the credentials match, the user session should be created. Let&#8217;s dive into the code again:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-274"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">publicKeyCredentialRequestOptions</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">challenge</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">Uint8Array</span><span class="token">.</span><span class="token" style="color: #6f42c1;">from</span><span class="token">(</span><span class="token" style="color: #24292e;">randomStringFromServer</span><span class="token">,</span><span class="token"> </span><span class="token">c</span><span class="token" style="color: #e36209;"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token" style="color: #24292e;">c</span><span class="token">.</span><span class="token" style="color: #6f42c1;">charCodeAt</span><span class="token">(</span><span class="token" style="color: #005cc5;">0</span><span class="token">)</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">allowCredentials</span><span class="token">:</span><span class="token"> </span><span class="token">[</span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">id</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">Uint8Array</span><span class="token">.</span><span class="token" style="color: #6f42c1;">from</span><span class="token">(</span><span class="token" style="color: #24292e;">credentialId</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #e36209;">c</span><span class="token"> </span><span class="token" style="color: #d73a49;">=&gt;</span><span class="token"> </span><span class="token" style="color: #24292e;">c</span><span class="token">.</span><span class="token" style="color: #6f42c1;">charCodeAt</span><span class="token">(</span><span class="token" style="color: #005cc5;">0</span><span class="token">)</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">type</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">public-key</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">transports</span><span class="token">:</span><span class="token"> </span><span class="token">[</span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">usb</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">ble</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">nfc</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">]</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">]</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">timeout</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #005cc5;">60000</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">assertion</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">await</span><span class="token"> </span><span class="token" style="color: #24292e;">navigator</span><span class="token">.</span><span class="token" style="color: #24292e;">credentials</span><span class="token">.</span><span class="token" style="color: #6f42c1;">get</span><span class="token">(</span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">publicKey</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">publicKeyCredentialRequestOptions</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-277"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-275">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-276'
	>
	The <code>challenge</code> is again part of the options, and it should again be received from the server, and passed back baked into the <code>assertion</code> later on. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-280"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-278">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-279'
	>
	The property <code>allowCredentials</code><em> </em>tells the browser which credentials should be used for the user authentication process. The <code>credentialId</code> retrieved and saved during registration is passed in here. This is useful if a device holds multiple credentials for different accounts because it excludes the ones not tied to the user account. Upon the initial request to the server, the server should provide the credential ID. This improves the user experience for users on a shared device.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-283"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-281">
	<h4	class='typography typography--size-30-text js-typography block-heading__heading'
	data-id='es-282'
	>
	Parsing and validating the login data</h4></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-286"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-284">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-285'
	>
	The response from the <code>navigator.credentials.get</code> is a <code>Promise</code> of <code>CredentialType</code>, or in our case, a <code>PublicKeyCredential</code>, and it looks like this:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-288"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #24292e;">PublicKeyCredential</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">i</span><span class="token" style="color: #6f42c1;">d</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">ADSUllKQmbqdGtpu4sjseh4cg2TxSvrbcHDTBsv4NSSX9...</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">r</span><span class="token" style="color: #6f42c1;">a</span><span class="token" style="color: #6f42c1;">wId</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ArrayBuffer</span><span class="token">(</span><span class="token" style="color: #005cc5;">59</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">resp</span><span class="token" style="color: #6f42c1;">o</span><span class="token" style="color: #6f42c1;">nse</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">AuthenticatorAssertionResponse</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #6f42c1;">authentic</span><span class="token" style="color: #6f42c1;">a</span><span class="token" style="color: #6f42c1;">torData</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ArrayBuffer</span><span class="token">(</span><span class="token" style="color: #005cc5;">191</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #6f42c1;">client</span><span class="token" style="color: #6f42c1;">D</span><span class="token" style="color: #6f42c1;">ataJSON</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ArrayBuffer</span><span class="token">(</span><span class="token" style="color: #005cc5;">118</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #6f42c1;">s</span><span class="token" style="color: #6f42c1;">i</span><span class="token" style="color: #6f42c1;">gnature</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ArrayBuffer</span><span class="token">(</span><span class="token" style="color: #005cc5;">70</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #6f42c1;">us</span><span class="token" style="color: #6f42c1;">e</span><span class="token" style="color: #6f42c1;">rHandle</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ArrayBuffer</span><span class="token">(</span><span class="token" style="color: #005cc5;">10</span><span class="token">)</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">,</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6f42c1;">t</span><span class="token" style="color: #6f42c1;">ype</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">public-key</span><span class="token" style="color: #032f62;">&#039;</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-291"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-289">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-290'
	>
	Most of the checks employed for registration should also be employed for user authentication. Validate the challenge, origin, RP ID, algorithms used, user verification (if used), and user presence flags.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-294"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-292">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-293'
	>
	The difference between registration and login is that the public key is not part of the response, as it has already been passed to the server in the registration flow. This is done by validating the <code>signature</code> from the response. A signature is a challenge signed with the private key associated with this credential, which is verified with the stored public key upon validation.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-297"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-295">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-296'
	>
	After successful validation, the server should create a session for the user and allow further access to the application. It’s good practice to remove the <code>challenge</code> after either successful or failed authentication to prevent replay attacks.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-300"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-298">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-299'
	>
	Account recovery</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-303"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-301">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-302'
	>
	If a user loses access to the device used for registration, they will not be able to access their account anymore. There are a few options to implement in this case. Good practice, or even a must-do in this scenario, is to enable the addition of multiple passkeys for the same account. Don’t forget to add the remove passkey option, so that the user is able to remove unused passkeys.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-306"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-304">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-305'
	>
	Using recovery codes to unlock the locked account is another way of dealing with lost passkeys. Recovery codes should be provided to the user in the registration flow, and the user should store them safely. This is good practice from a security standpoint, but users tend to forget or ignore the recovery codes.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-309"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-307">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-308'
	>
	One of the options is to use a two-factor authentication account recovery, where the user would have to verify their identity via two authentication factors, like email and text. However, this forces users to enter additional sensitive information, and not all users will be willing to do that.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-312"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-310">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-311'
	>
	Passkeys support</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-315"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-313">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-314'
	>
	Passkeys are supported on <a href="https://caniuse.com/?search=webauthn" target="_blank" rel="noreferrer noopener">all major browsers</a>. To make sure that the passkeys are supported on the user’s browser, you should always check the availability of a platform authenticator using <code>isUserVerifyingPlatformAuthenticatorAvailable</code> and check if the conditional mediation is available with <code>isConditionalMediationAvailable</code>. Conditional mediation allows websites to make a WebAuthn request and autofill the form with the passkey.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-317"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">if</span><span class="token"> </span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #24292e;">window</span><span class="token">.</span><span class="token" style="color: #24292e;">PublicKeyCredential</span><span class="token"> </span><span class="token" style="color: #d73a49;">&amp;&amp;</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #24292e;">PublicKeyCredential</span><span class="token">.</span><span class="token" style="color: #24292e;">isUserVerifyingPlatformAuthenticatorAvailable</span><span class="token"> </span><span class="token" style="color: #d73a49;">&amp;&amp;</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #24292e;">PublicKeyCredential</span><span class="token">.</span><span class="token" style="color: #24292e;">isConditionalMediationAvailable</span><span class="token">
</span></span><span class="line"><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Check if user verifying platform authenticator is available.</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">isWebAuthnSupported</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">await</span><span class="token"> </span><span class="token" style="color: #24292e;">PublicKeyCredential</span><span class="token">.</span><span class="token" style="color: #6f42c1;">isUserVerifyingPlatformAuthenticatorAvailable</span><span class="token">(</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">const</span><span class="token"> </span><span class="token" style="color: #005cc5;">isConditionalMediationSupported</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #d73a49;">await</span><span class="token"> </span><span class="token" style="color: #24292e;">PublicKeyCredential</span><span class="token">.</span><span class="token" style="color: #6f42c1;">isConditionalMediationAvailable</span><span class="token">(</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #d73a49;">if</span><span class="token"> </span><span class="token">(</span><span class="token" style="color: #24292e;">isWebAuthnSupported</span><span class="token"> </span><span class="token" style="color: #d73a49;">&amp;&amp;</span><span class="token"> </span><span class="token" style="color: #24292e;">isConditionalMediationSupported</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Display &quot;Create a new passkey&quot; button</span><span class="token">
</span></span><span class="line"><span class="token"> </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-320"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-318">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-319'
	>
	Get ready for the passwordless future</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-323"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-321">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-322'
	>
	It is becoming evident that the path to user-friendly and secure web authentication lies in the implementation of passkeys.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-326"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-324">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-325'
	>
	By following the defined standards and best practices for using the Web Authentication API described above, developers can offer a great alternative to passwords and provide the users of their web apps with a seamless authentication flow.</p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/secure-web-authentication-passkeys-webauthn/">Secure Web Authentication  – Passkeys &amp; Web Authentication API</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
					<item>
				<image>
					<url>19253793https://infinum.com/uploads/2024/05/A_Guide_to_Password-Less_Login_Methods_blogpost-hero-ig-min.webp</url>
				</image>
				<title>Out with ‘12345’ – Discover Passwordless Authentication Methods</title>
				<link>https://infinum.com/blog/passwordless-authentication-methods/</link>
				<pubDate>Mon, 06 May 2024 14:22:35 +0000</pubDate>
				<dc:creator>Tomislav Habalija</dc:creator>
				<guid isPermaLink="false">https://infinum.com/?p=19253793</guid>
				<description>
					<![CDATA[<p>Enhance your app's security and user experience with passwordless authentication methods. Say goodbye to passwords and hello to convenience.</p>
<p>The post <a href="https://infinum.com/blog/passwordless-authentication-methods/">Out with ‘12345’ – Discover Passwordless Authentication Methods</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-506"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-329">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-332"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-330">
	<p	class='typography typography--size-36-text js-typography block-paragraph__paragraph'
	data-id='es-331'
	>
	<strong>Passwords are inherently flawed, both in terms of security and user experience. We present a range of passwordless authentication methods you can use to make your app secure and user-friendly.&nbsp;</strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-335"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-333">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-334'
	>
	In today&#8217;s digital-first world, passwords are keys for unlocking the information you desire. A password is a single key shared between the user and the service. It is often the only thing hackers need to access user bank accounts, social media information, and other sensitive data.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-338"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-336">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-337'
	>
	As the number of online services grows, remembering multiple complex passwords has become increasingly challenging, especially if you don&#8217;t use password managers.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-341"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-339">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-340'
	>
	Studies in recent years have shown that people are using passwords incorrectly. For example, <a href="https://newsroom.ibm.com/2021-06-15-IBM-Survey-Pandemic-Induced-Digital-Reliance-Creates-Lingering-Security-Side-Effects">IBM’s global survey</a> found that 82% of respondents reused passwords at least once. Even worse, 21% reuse the same username and password for every new account. This poses a serious security risk. According to the <a href="https://www.verizon.com/business/resources/reports/dbir/" target="_blank" rel="noreferrer noopener">Verizon Data Breach Investigations Report for 2024</a>, stolen credentials account for a whopping 77% of basic web application attacks. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-344"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-342">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-343'
	>
	Fortunately, passwords are not the only authentication solution out there. There is a range of passwordless authentication methods at our disposal, and the technology supporting them has been available for some time now. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-347"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-345">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-346'
	>
	In this article, we will discuss passwordless authentication solutions for the web, how to use them, their benefits and pitfalls, and how they improve security and user experience.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-354"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<a	class="card-simple js-card-simple card-simple--is-ad block-card__card-simple card-simple--has-link js-card-simple-link card-simple__content-align--left"
	data-id="es-348"
	 target='_blank' rel='noopener noreferrer' href='https://infinum.com/cybersecurity/penetration-testing/'>

	
	
	<div class="card-simple__content">
		<div class="card-simple__heading-wrap">
			<h2	class='typography typography--size-24-text js-typography card-simple__heading'
	data-id='es-349'
	>
	Securing your business from cyberattacks requires a holistic approach. While passwordless authentication strengthens user access security, penetration testing goes deeper by identifying hidden vulnerabilities across your systems. Our experts simulate real-world attacks to ensure your app is protected from all angles.</h2>		</div>

		<button	class="btn btn--color-infinum btn--size-small btn--width-default btn__icon-position--right card-simple__btn js-block-card-btn js-card-simple-link"
	data-id="es-351"
	 tabindex='-1'>
		<div class="btn__inner">
					<div	class='typography typography--size-none js-typography btn__label'
	data-id='es-352'
	>
	Find out more </div>		
		<i
	class="icon btn__icon icon--size-16 icon--scale-100"
	 aria-hidden='true' data-name='arrow-right-16' data-id='es-353'>
	<svg fill='none' height='16' viewBox='0 0 17 16' width='17' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'><g stroke='currentColor' stroke-width='2'><path d='m.5 7.99999 14 .00001'/><path d='m9.23352 2.7251 5.97848 5.97852'/><path d='m9.23352 13.2744 5.97848-5.9785'/></g></svg></i>	</div>
	</button>	</div>
</a>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-357"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-355">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-356'
	>
	What is authentication?</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-360"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-358">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-359'
	>
	Authentication is the process of verifying that the user is who they claim to be. There are three common authentication factors:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-363"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-361">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-362'
	>
	<li>Something you know, like a password</li><li>Something you have, like a device</li><li>Something you are, like a fingerprint</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-366"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-364">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-365'
	>
	When users register for a service, they usually need to provide a username or email address and a password. <strong>Identification</strong> occurs upon login, when a user enters a username/email address, and <strong>authentication</strong> occurs when users prove their identity with authentication factors. For example, users are authenticated when they provide their username and correct password. Permissions of the service are then granted based on their proven identity.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-369"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-367">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-368'
	>
	<strong>Two-factor authentication (2FA)</strong> or <strong>multi-factor authentication (MFA)</strong> is a process where the service checks for two or more authentication factors during the user authentication process. For example, along with the password, the user has to click on a link in an email or enter a one-time passcode received via a text message. Multi-factor authentication ensures that even if one factor is compromised, unauthorized access is still unlikely.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-372"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-370">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-371'
	>
	Why passwords are flawed</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-375"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-373">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-374'
	>
	A password is secret data, typically a string of characters, usually used to confirm a user&#8217;s identity. Using passwords, in general, is not a bad concept. Implementing password-based authentication is fast and simple and gets the job done. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-378"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-376">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-377'
	>
	However, fast and simple as it may be, <strong>using passwords as a single point of user authentication can be insecure</strong>. There are ways to improve password security, from validating the password&#8217;s length and character diversity to requiring special characters, for example. We can also scramble the password, for instance, by salting and peppering it upon saving it in the database. This modifies the password by adding a set of characters and encrypting it with a one-way function to make it unrecognizable and hard to decrypt. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-381"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-379">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-380'
	>
	The second problem with password-based authentication is the <strong>people factor.</strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-383"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-382'
	>
	<strong>People often use weak passwords so they can remember them, or even worse – they tend to reuse passwords across multiple services. If one service gets hacked and passwords leak, all other services become compromised, too. </strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-386"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-384">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-385'
	>
	Even worse, if the leaked password is the same as the user&#8217;s email password, the hacker can access all services connected to that email. For example, they could request a password reset on all services connected to the user&#8217;s email and set a new password.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-389"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-387">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-388'
	>
	One way to prevent this is two-factor authentication, commonly known as 2FA. The first authentication factor is a password, and the second one can be email or text message confirmation, device authentication, or similar methods. This improves security, but the process is more complicated for the user.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-392"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-390">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-391'
	>
	Passwordless authentication methods for the web</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-395"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-393">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-394'
	>
	We&#8217;ve concluded that password-based authentication is not the best option, either from a security or user experience standpoint. Fortunately, there are also passwordless authentication methods. Choosing which one to use will depend on the needs of the application you are building and whether you need to prioritize security or the user experience because one often comes at the expense of the other. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-398"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-396">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-397'
	>
	OAuth</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-401"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-399">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-400'
	>
	User authentication can be done through a third-party service provider, for example, by <a href="https://infinum.com/blog/secure-token-storage-oauth2/" target="_blank" rel="noreferrer noopener">implementing the OAuth2 protocol</a>. When a user requests authentication, they are redirected to the third-party service provider. The service provider performs the authentication, creating a session for the user.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-404"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-402">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-403'
	>
	This passwordless authentication method is simple to implement and simple to use from the user&#8217;s side. Its most common implementation is login via Google or Facebook. Using this approach removes the need to store the passwords on your server, as a third-party service handles that part, but in the end, the service still has to validate the user. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-407"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-405">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-406'
	>
	One drawback of this passwordless method is user privacy concerns, as some users may not be happy with linking their Google or Facebook accounts to your application and sharing their data.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-410"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-408">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-409'
	>
	Magic links</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-413"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-411">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-412'
	>
	Magic links are a token-based passwordless authentication solution that uses a <strong>unique, time-sensitive URL</strong>, which provides <strong>a token to serve as a credential for user authentication</strong>. The magic link is sent to the user&#8217;s registered email or phone number. When a user clicks on a magic link, the embedded token is validated against the server to authenticate the user&#8217;s identity. This process, by design, eliminates the traditional risks associated with password-based authentication and simplifies the login experience.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-416"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-414">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-415'
	>
	Each magic link is a <strong>one-time authentication</strong>, somewhat similar to a one-time password (OTP). The major difference is that unlike OTP, where the user has to type in the password they received, with a magic link, they don&#8217;t need to input any information, as the token will be extracted from the link itself.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-419"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-417">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-418'
	>
	The magic link approach offers a solid passwordless experience. But is it safe? Magic links tend to be valid for a short period, meaning the attacker has only a small window of opportunity to intercept one. In essence, <strong>magic link authentication is as safe as the service the magic link is sent to</strong>, as that service performs the user authentication. If an attacker gets into that service, entering the service that uses magic links is as easy as re-login.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-422"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-420">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-421'
	>
	One-time passwords</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-425"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-423">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-424'
	>
	OTP or one-time passwords are mainly used as part of multi-factor authentication, not as a single authentication method. During the login flow, the user receives an email or text with a unique combination of characters called a one-time password. This authenticates the user for a single login session. In addition to email and texts, there is a third option – authenticator apps like Google Authenticator, Microsoft Authenticator, and various others.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-427"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-426'
	>
	<strong>The UX disadvantage of using one-time passwords is that users have to remember them. OTPs are short-lived by nature, and the pressure is on the user to remember them and enter them quickly before they expire. </strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-430"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-428">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-429'
	>
	This likely won&#8217;t present much of an issue for an average user, but can become very challenging for an elderly person or someone with a disability. In any case,  it is always good practice to allow users to paste the copied one-time password into the application.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-433"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-431">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-432'
	>
	The security and user experience of this method can be slightly improved by implementing <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebOTP_API" target="_blank" rel="noreferrer noopener">WebOTP API</a> &#8211; an extension of <a href="https://developer.mozilla.org/en-US/docs/Web/API/Credential_Management_API" target="_blank" rel="noreferrer noopener">Credential Management API</a>. The WebOTP API provides a streamlined user experience for web apps to verify that a phone number belongs to a user when using it as a sign-in factor. The downside is that the support is not available across all major browsers, so some users will not have access to this user experience enhancement.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-436"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-434">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-435'
	>
	The flow is straightforward – a user enters a username, email, or phone number used for the service, and then they receive the one-time password via email or text, which serves as an additional authentication factor. With an authenticator app, the user needs to open the app on the linked device and enter the OTP into the login form, which is then sent to the server for verification.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-439"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-437">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-438'
	>
	In the case of WebOTP API, if the text message is formatted in a defined way, the browsers that support this can automatically prompt the user to enter the one-time password into the login flow. The security is slightly improved with WebOTP API as the domain part will render phishing attacks impossible since the OTP is tied to the domain.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-442"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-440">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-441'
	>
	Passkeys</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-445"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-443">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-444'
	>
	Passkeys are <strong>the beginning of the end of passwords</strong>. They are the most secure way of user authentication and provide a seamless experience – a significant step toward a passwordless future. Most people already own and use the technology that can be used for authentication through passkeys. For example, this would be a fingerprint reader or a facial recognition system like Apple&#8217;s Face ID or Android facial recognition. Another example is software-based solutions like a browser profile, such as a Google Chrome browser profile. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-447"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-446'
	>
	<strong>The passkey is a credential, a public-private key pair generated by an authenticator, used to access the application. The private key is stored securely on the user&#8217;s device while the public key is sent to the server. </strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-450"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-448">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-449'
	>
	During the registration process, the user associates the public key with the account, and upon login, the public key is verified against the user&#8217;s private key on the platform authenticator.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-453"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-451">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-452'
	>
	The perspective in this passwordless solution is that the server does not need to know who the user is specifically, just that they are the same one who created the account, and that part is verified on the platform itself. This means that the fingerprint used for verifying the user in the authentication process <strong>never leaves the platform, and it is not shared anywhere</strong>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-456"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-454">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-455'
	>
	Syncing passkeys</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-459"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-457">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-458'
	>
	What happens if I lose or replace the device that contains my passkey credential? Since <strong>passkeys are tied to a single device</strong>, users need to <strong>register multiple devices to ensure access </strong>to the application if one of them gets terminated. This can pose a user experience problem, as now the user has to own and register multiple devices.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-462"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-460">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-461'
	>
	Here is where large platforms and operating systems come into the picture. They use their own secure channels to sync passkeys across multiple user devices in the background, making them available across all of the user&#8217;s compatible devices. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-465"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-463">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-464'
	>
	For example, Apple uses its iCloud Keychain, and Google uses Google Password Manager. The downside to this passwordless experience is vendor lock-in because it is complicated to move out of a company&#8217;s ecosystem if you decide to make a switch. Moving to another passkey manager would require changing all passkeys on all services.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-468"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-466">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-467'
	>
	Web authentication API</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-471"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-469">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-470'
	>
	WebAuthn (short for <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API" target="_blank" rel="noreferrer noopener">Web Authentication API</a>) provides a passwordless authentication method using hardware or software-based authenticators like USB security keys or fingerprint readers. An extension of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Credential_Management_API" target="_blank" rel="noreferrer noopener">Credential Management API</a>, it can provide your users with a more secure and user-friendly authentication process. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-474"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-472">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-473'
	>
	The API specification enables applications to use strong and secure authentication methods for user registration and the login process. It enables passwordless authentication and secure multi-factor authentication (MFA) without needing a text or an authenticator application.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-477"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-475">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-476'
	>
	These authenticators rely on public-key cryptography to provide secure registration and authentication of accounts. To achieve this, the user has to complete a sequence of steps to associate an authenticator device with their account, which generates a public-private key pair. Using this way of passwordless authentication, by nature, has some benefits:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-481"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-478">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<h3	class='typography typography--size-24-text js-typography bullet__heading'
	data-id='es-479'
	>
	<strong>Phishing protection</strong></h3><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-480'
	>
	An attacker who creates a phishing website with a fake login can&#8217;t log in as the user because the signature changes with the website&#8217;s origin. </p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-485"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-482">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<h3	class='typography typography--size-24-text js-typography bullet__heading'
	data-id='es-483'
	>
	<strong>Reduced impact of data breaches</strong></h3><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-484'
	>
	With this passwordless method, the only information an attacker can get from a data breach is the user’s public key, rendering the data worthless.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-489"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-486">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<h3	class='typography typography--size-24-text js-typography bullet__heading'
	data-id='es-487'
	>
	<strong><strong>Invulnerable to password attacks</strong></strong></h3><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-488'
	>
	As the passkey is unique to the service, if the attacker obtains one service&#8217;s passkeys, they are useless for another website, which is not the case with passwords.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-492"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-490">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-491'
	>
	Bonus material for the curious: Public key cryptography explained</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-495"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-493">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-494'
	>
	The key concept in understanding passkeys is public-key cryptography or asymmetric cryptography. We can explain this simply with a color-mixing analogy. When you mix two colors, you get a new color. However, guessing the exact colors that created the new one is nearly impossible.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-498"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-496">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-497'
	>
	Complementary colors are pairs of colors that, when combined or mixed, cancel each other out (lose hue) by producing a grayscale color like white or black. For this example, we will use a color mixer that produces a complementary color of the starting one. In this example, we assume that mixing colors is a one-way function as it is fast to mix colors and output a third color, and it is much slower to undo.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-501"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-499">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-500'
	>
	Let&#8217;s say two parties, Alice and Bob, want to start an encrypted conversation. Alice starts by selecting a color: red.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-504"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-502">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-503'
	>
	<strong>Step 1:</strong> Alice mixes the private red color with the color mixer to create a complementary color—cyan. She then sends the cyan to Bob.</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-509"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-507"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-508">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2024/05/A_Guide_to_Password-in-article_1-min-1400x410.webp				media='(max-width: 699px)'
				type=image/webp								height="410"
												width="1400"
				 />
												<img
					src="https://infinum.com/uploads/2024/05/A_Guide_to_Password-in-article_1-min.webp"
					class="image__img block-media__image-img"
					alt=""
										height="540"
															width="1846"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-515"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-510">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-513"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-511">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-512'
	>
	<strong>Step 2:</strong> Bob wants to send Alice a yellow message, so he adds yellow to the mixture Alice sent him, creating a green color, and sends it to Alice.</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-518"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-516"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-517">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2024/05/A_Guide_to_Password-in-article_2-min-1400x574.webp				media='(max-width: 699px)'
				type=image/webp								height="574"
												width="1400"
				 />
												<img
					src="https://infinum.com/uploads/2024/05/A_Guide_to_Password-in-article_2-min.webp"
					class="image__img block-media__image-img"
					alt=""
										height="757"
															width="1846"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-524"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-519">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-522"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-520">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-521'
	>
	<strong>Step 3</strong>: Alice adds her private red color to the mixture, and since this is a complementary color to the cyan, it cancels the cyan out, leaving the clear yellow color – Bob&#8217;s message.</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-527"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-525"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-526">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2024/05/A_Guide_to_Password-in-article_3-min-1400x746.webp				media='(max-width: 699px)'
				type=image/webp								height="746"
												width="1400"
				 />
												<img
					src="https://infinum.com/uploads/2024/05/A_Guide_to_Password-in-article_3-min.webp"
					class="image__img block-media__image-img"
					alt=""
										height="984"
															width="1846"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-557"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-528">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-531"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-529">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-530'
	>
	Suppose a third person wants to listen in on the conversation at any point. The information they receive will be useless, as it is encrypted. The key ingredient for decrypting it exists only in Alice&#8217;s private space. This is also why private keys should always be safely stored and never shared publicly.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-534"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-532">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-533'
	>
	This oversimplified key exchange example is used just for explanation purposes. The real use case is more complicated but shares the same concepts. One of the key points is the idea of mixing the colors, as this process is &#8220;non-reversible.&#8221;&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-537"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-535">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-536'
	>
	In theory, reverse-engineering a private key is possible, but it would require significant resources. The encryption algorithm used is one-way, meaning it can realistically only be cracked through a brute force attack, which involves guessing the private key. This process is so resource-intensive that it’s simply not worth the effort.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-540"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-538">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-539'
	>
	One of the most common algorithms used for asymmetric encryption on the web is RSA. It is used in web browsers, email chats, VPNs, and other communication channels. It has been used multiple times since you opened the browser and navigated to this blog post.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-543"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-541">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-542'
	>
	Choose the passwordless authentication method that fits your needs</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-546"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-544">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-545'
	>
	The technology behind passwordless authentication methods has been around for some time, and big players like Google and Microsoft have already adopted it. The main goal is to improve both security and user experience, which can be achieved by following the defined standards.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-549"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-547">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-548'
	>
	Improving the user experience and simplifying the registration process are key ingredients in acquiring new users. That said, deciding which solution to implement depends on the project you are working on – there is no universal solution for all applications. Sometimes, the emphasis is on the user experience and sometimes on raising the security level. Higher security, unfortunately, translates to more steps, which complicates the flow, but by taking the right steps, we can implement the solution so that users can easily authenticate and enjoy the passwordless experience.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-552"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-550">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-551'
	>
	The Web authentication API is ready to be used by developers, and it is already backed with a lot of examples on the web, implemented in various programming languages. Don&#8217;t be a late adopter.  </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-555"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-553">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-554'
	>
	<em>And in case you need help boosting your security posture, check out our <a href="https://infinum.com/cybersecurity/" target="_blank" rel="noreferrer noopener">cybersecurity services</a>.</em></p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/passwordless-authentication-methods/">Out with ‘12345’ – Discover Passwordless Authentication Methods</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
					<item>
				<image>
					<url>50486https://infinum.com/uploads/2024/02/debugging-hero-min.webp</url>
				</image>
				<title>3 Tricky Issues in Frontend Development and How to Debug Them</title>
				<link>https://infinum.com/blog/debugging-3-toughest-frontend-issues/</link>
				<pubDate>Fri, 09 Feb 2024 16:14:16 +0000</pubDate>
				<dc:creator>Tomislav Habalija</dc:creator>
				<guid isPermaLink="false">https://infinum.com/?p=50486</guid>
				<description>
					<![CDATA[<p>How do you debug when you can’t seem to catch the bug? Here's how we solved a complex frontend issue involving CORS, cache invalidation, and naming. </p>
<p>The post <a href="https://infinum.com/blog/debugging-3-toughest-frontend-issues/">3 Tricky Issues in Frontend Development and How to Debug Them</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-732"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-558">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-561"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-559">
	<p	class='typography typography--size-36-text js-typography block-paragraph__paragraph'
	data-id='es-560'
	>
	<strong>How do you debug when you can’t seem to catch the bug? During our work, we came across a complex issue involving CORS, cache invalidation, and naming – a rare find in frontend development. We present our path to the solution.&nbsp;</strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-564"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-562">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-563'
	>
	Debugging software issues can be both challenging and rewarding. However, when not done systematically, it is easy to lose yourself in the maze of errors, symptoms, and possible solutions.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-567"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-565">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-566'
	>
	Sometimes, tracking that one elusive issue can make an engineer feel like they should consider a career switch to forensics. The story we’re about to share is a perfect example of this – a real-world debugging experience that revolved around one frustrating CORS (Cross-Origin Resource Sharing) issue.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-570"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-568">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-569'
	>
	The idea is to demonstrate the thought process and methods used to identify and ultimately resolve the problem. Whether you are new to frontend development or an experienced senior, there’s something to learn from this journey.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-573"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-571">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-572'
	>
	What is CORS?</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-576"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-574">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-575'
	>
	To follow this story, it’s important first to understand what CORS is and what’s its purpose.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-579"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-577">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-578'
	>
	Cross-Origin Resource Sharing (CORS) is a security feature implemented by web browsers to restrict web pages from making requests to a different domain than the one that served the original web page. It is a set of HTTP headers that allows or denies web browsers to execute cross-origin requests based on the policy defined by the server.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-582"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-580">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-581'
	>
	CORS is crucial for web security and helps prevent potential security vulnerabilities, such as cross-site request forgery (CSRF) and cross-site scripting (XSS).</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-585"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-583">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-584'
	>
	A bug in the image editor</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-588"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-586">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-587'
	>
	With the theoretical part covered, we can delve into the issue.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-591"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-589">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-590'
	>
	During our project work, we were developing a content management application for a client. The application also includes an image editor, used for attaching an image to the content. On the image editor page, users are able to select one of the previously uploaded images for additional editing, for example, cropping. The selection is made by fetching the image via the <code>fetch</code> method, and the image is used to populate the <code>canvas</code> element later on.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-594"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-592">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-593'
	>
	This was the part of the app that contained a bug.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-597"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-595">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-596'
	>
	The mysterious report with a CORS error</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-600"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-598">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-599'
	>
	It all started with a user report stating that the image could not be selected. When something like this happens, the first thing to do is to check the error monitoring system for errors. In our case, the logs were showing a CORS error.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-603"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-601">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-602'
	>
	This is where the real mystery began, as our app had been up and running for some time, and the CORS should not have been the problem. At least, that is what we thought.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-606"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-604">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-605'
	>
	When reproducing the bug doesn’t work</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-609"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-607">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-608'
	>
	After not being able to reproduce the issue in an isolated environment, we jumped into the production environment to examine the issue. A perplexing challenge presented itself – the bug was not reproducible.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-612"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-610">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-611'
	>
	Everything appeared to work fine from the developer&#8217;s standpoint; the problem couldn&#8217;t be reproduced by us or the user anymore. Nevertheless, the issue persisted, and it resurfaced a week later, leaving both the user and us baffled.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-615"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-613">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-614'
	>
	To get to the answer, you need to be asking the right questions</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-618"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-616">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-617'
	>
	A key breakthrough came through the process of asking questions and retrieving user feedback combined with thorough testing.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-621"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-619">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-620'
	>
	Reaching out to users is always a great idea. Only when we attempted to replicate the entire workflow fully did the error finally surface. We discovered that the error occurred only after the user completed a series of specific steps. The end result was the error present in our console:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-623"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #24292e;">No</span><span class="token"> </span><span class="token" style="color: #032f62;">&#039;</span><span class="token" style="color: #032f62;">Access-Control-Allow-Origin</span><span class="token" style="color: #032f62;">&#039;</span><span class="token"> </span><span class="token" style="color: #24292e;">header</span><span class="token"> </span><span class="token" style="color: #24292e;">is</span><span class="token"> </span><span class="token" style="color: #24292e;">present</span><span class="token"> </span><span class="token" style="color: #24292e;">on</span><span class="token"> </span><span class="token" style="color: #24292e;">the</span><span class="token"> </span><span class="token" style="color: #24292e;">requested</span><span class="token"> </span><span class="token" style="color: #24292e;">resource</span><span class="token">.</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-626"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-624">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-625'
	>
	Isolating the issue</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-629"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-627">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-628'
	>
	After defining the right steps to reproduce the bug in an isolated environment, the key thing to learn is the minimal set of steps that lead to the bug.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-632"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-630">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-631'
	>
	When we eliminated all of the unnecessary steps, the bug was revealed. It was reproducible if the user viewed the image somewhere in the app before selecting it in the image editor.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-635"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-633">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-634'
	>
	Knowing that the prerequisite for reproducing the bug was viewing the image before selection, the next step was to jump into developer tools, the network tab, and examine the requests leading to the bug. How was it possible that the image that we were displaying suddenly broke CORS?</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-638"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-636">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-637'
	>
	Uncovering the cache problem</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-641"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-639">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-640'
	>
	When we were inspecting the request that failed with the CORS error, one thing stood out – the “Provisional headers are shown” warning message in the developer console. This meant that the browser returned the request from the cache instead of creating a new request.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-644"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-642">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-643'
	>
	The question now was – how come the request returned from the cache did not trigger the CORS issue upon the initial request? We tried disabling the cache to see if the issue was still reproducible, and you can probably already guess it – it wasn’t.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-647"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-645">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-646'
	>
	Understanding the core issue</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-650"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-648">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-649'
	>
	The key revelation was that the response headers differed when fetching the image via an <code>image</code> tag:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-652"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-php github-light" data-language="php" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">&lt;</span><span class="token" style="color: #005cc5;">img</span><span class="token"> </span><span class="token" style="color: #005cc5;">src</span><span class="token" style="color: #d73a49;">=</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">image.jpg</span><span class="token" style="color: #032f62;">&quot;</span><span class="token"> </span><span class="token" style="color: #d73a49;">/</span><span class="token" style="color: #d73a49;">&gt;</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-655"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-653">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-654'
	>
	As compared to using JavaScript via a <code>fetch</code> method:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-657"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">fetch</span><span class="token">(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">image.jpg</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">;</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-660"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-658">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-659'
	>
	Notably, the</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-662"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #24292e;">Access</span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #24292e;">Control</span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #24292e;">Allow</span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #6f42c1;">Origin</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #d73a49;">*</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-665"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-663">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-664'
	>
	header was absent in responses when fetched via an <code>image</code> tag.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-668"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-666">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-667'
	>
	This disparity in response headers meant that when an image was initially rendered on the UI and subsequently fetched via JavaScript, it became cached with the response header:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-670"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #24292e;">Cache</span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #6f42c1;">Control</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">public</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #24292e;">max</span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #24292e;">age</span><span class="token" style="color: #d73a49;">=</span><span class="token" style="color: #005cc5;">31557600</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-673"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-671">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-672'
	>
	This indicated a cache duration of one year. When a JavaScript <code>fetch</code> call attempted to access the cached image, the missing</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-675"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #24292e;">Access</span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #24292e;">Control</span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #24292e;">Allow</span><span class="token" style="color: #d73a49;">-</span><span class="token" style="color: #6f42c1;">Origin</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #d73a49;">*</span><span class="token"> 
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-678"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-676">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-677'
	>
	header triggered the CORS error.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-681"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-679">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-680'
	>
	The solution</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-684"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-682">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-683'
	>
	In the world of software development, cache invalidation is usually tricky to handle. There are multiple ways of solving this problem. Let’s go through some of them.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-687"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-685">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-686'
	>
	If headers for the <code>fetch</code> method for fetching the image via JavaScript were updated to contain <code>Cache: no store</code>, the problem would be resolved. The same result would be accomplished by adding a timestamp as a query parameter to the request. However, this method comes with a downside – this request would never be cached, meaning that the next time the app fetches an image like this, the request would also be sent to the backend, even if the valid request is already present in the cache.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-690"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-688">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-689'
	>
	The more correct approach would be to implement a query parameter that is constantly the same. This brings us to another complex issue in the development world – naming. How to name such a parameter? For example, adding <code>provisional: true</code> to the request from fetch would ignore the cache for the first time but would return the cached request every other time.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-693"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-691">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-692'
	>
	The best approach requires some server setup, or to be exact, the ability to add specific response headers. This approach includes adding a header to the response:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-695"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">Vary</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #24292e;">Accept</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-698"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-696">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-697'
	>
	This way, the browser knows when to ignore the cached request. The Vary header tells the browser that the requests may vary depending on the value that you set in the header.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-701"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-699">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-700'
	>
	For example, the request that originates from an <code>image</code> tag will have the <code>Accept</code> header value set to <code>image/avif</code>, <code>image/webp</code>, <code>image/apng</code>, <code>image/svg+xml</code>…, and if you’re using <code>fetch</code>, you can set the value to <code>image/jpeg</code>. Based on that, the browser will cache the request depending on the URL and the value of the <code>Accept</code> header.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-704"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-702">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-703'
	>
	In our example, we went with the addition of a query parameter:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-706"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6f42c1;">provisional</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #005cc5;">true</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-709"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-707">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-708'
	>
	We decided on this approach because we were using an Amazon S3 bucket for serving the images, and <code>Vary</code> is not a user-configurable header. A different file storage service might allow setting the <code>Vary</code> response header. If it does, that would be the preferred solution.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-712"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-710">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-711'
	>
	Underlying concepts</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-715"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-713">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-714'
	>
	As the first letter in REST (representational state transfer) implies, the requested resource can be represented in many different ways. The same API endpoint can return a different representation of the same resource, depending on the way the resource is requested.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-718"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-716">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-717'
	>
	In our example, the same image was returned as content, but REST allows for much more. For example, the image can be requested with different values in the <code>Accept</code> header, like <code>image/png or image/webp</code>, and the API should then be able to return the image in other formats.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-721"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-719">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-720'
	>
	One of the common uses of the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation" target="_blank" rel="noreferrer noopener">Content Negotiation principle</a> is to apply the <code>Accept-language</code> header based on which the resource will be translated. Some browsers will send this header by default, filled with the value from the browser settings. The <code>Vary</code> header also plays a role in this, as we’ve seen in the example provided.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-724"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-722">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-723'
	>
	Mystery resolved</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-727"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-725">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-726'
	>
	This debugging journey teaches us the importance of thorough investigation, asking the right questions, and understanding the intricacies of the technologies we work with.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-730"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-728">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-729'
	>
	Mysterious issues can be tricky, but with patience and persistence, combined with a systematic debugging approach, they can be overcome. This at least applies to the CORS and cache issues. The naming will always be tricky.</p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/debugging-3-toughest-frontend-issues/">3 Tricky Issues in Frontend Development and How to Debug Them</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
					<item>
				<image>
					<url>38525https://infinum.com/uploads/2023/05/Development-documentation-hero.webp</url>
				</image>
				<title>Creating Software Documentation that Saves Time and Money</title>
				<link>https://infinum.com/blog/software-documentation/</link>
				<pubDate>Thu, 11 May 2023 13:52:03 +0000</pubDate>
				<dc:creator>Tomislav Habalija</dc:creator>
				<guid isPermaLink="false">https://infinum.com/?p=38525</guid>
				<description>
					<![CDATA[<p>Many developers find documentation writing tedious and time-consuming, but the truth is software documentation is critical to the success of any project.</p>
<p>The post <a href="https://infinum.com/blog/software-documentation/">Creating Software Documentation that Saves Time and Money</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-938"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-733">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-736"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-734">
	<p	class='typography typography--size-36-text js-typography block-paragraph__paragraph'
	data-id='es-735'
	>
	In the fast-paced world of software development, it&#8217;s easy to overlook the importance of documentation. Many developers view it as a tedious and time-consuming task, but the truth is, documentation is critical to the success of any project. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-739"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-737">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-738'
	>
	If you want to build great digital products, your documentation needs to follow suit. Without proper documentation, projects can quickly spiral out of control, leading to missed deadlines, increased costs, and frustrated stakeholders.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-742"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-740">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-741'
	>
	In this article we take you through the most common types of documentation, explain how taking the time to document your work actually saves time and money in the long run, and provide tips for mastering the art of documentation writing. Should you lean on AI assistants for help? We tackle that question too.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-745"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-743">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-744'
	>
	Let’s get documenting.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-748"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-746">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-747'
	>
	Documentation doesn’t have to be a chore</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-751"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-749">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-750'
	>
	Many developers view documentation as a chore, something that takes away from the more exciting and challenging work of writing code. They may feel that documentation is a distraction from the creative and technical aspects of software development, or that it doesn&#8217;t directly contribute to the functionality of the software. However, this mindset is short-sighted and ultimately leads to more problems down the road.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-754"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-752">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-753'
	>
	Great documentation is the basis for great software</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-757"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-755">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-756'
	>
	Documentation serves as a roadmap for the development process, providing clear and concise information about how a project is intended to work and how it was implemented. This information is essential for maintaining and updating the software over time, as well as for onboarding new team members. Good documentation helps to ensure that everyone is on the same page, reducing the risk of misunderstandings and errors.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-760"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-758">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-759'
	>
	In addition, documentation helps to improve the overall quality of software. By providing clear and detailed information about the design and implementation of software, developers can more easily identify and resolve issues, resulting in a better and more reliable product.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-763"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-761">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-762'
	>
	Types of documentation</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-766"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-764">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-765'
	>
	When referring to documentation, most of the time people will think about the most commonly used types – code documentation or API documentation. But in fact, there are multiple types of documentation in the world of software development, and other variants can also be crucial for project scalability and maintainability.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-769"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-767">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-768'
	>
	Let’s look at a few of the most common types of documentation.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-772"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-770">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-771'
	>
	Process Documentation</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-775"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-773">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-774'
	>
	Process documentation refers to documentation that outlines the various steps and procedures involved in completing a specific task or process. This can include anything from onboarding new people to the project to creating a marketing campaign. Process documentation helps to ensure consistency and efficiency in completing tasks, and can be used as a reference for training new people.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-778"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-776">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-777'
	>
	Code Documentation</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-781"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-779">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-780'
	>
	It’s the type of documentation that provides information about the code itself, including variables, functions, and classes. This type of documentation typically includes comments within the code itself, as well as separate documentation files like API documentation. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-783"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-782'
	>
	<strong>Code documentation helps to ensure that the code is maintainable, readable, and understandable, making it easier to modify or debug as needed.</strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-786"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-784">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-785'
	>
	Well-written end-to-end, integration, and unit tests can also be considered code documentation, as they are executable and verifiable documentation that can be read as if you were reading system requirements.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-789"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-787">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-788'
	>
	The bare minimum for code documentation is a readme file that contains information about how to run the project, and how to contribute to it. Contribution guidelines might include things like information on coding standards and branching/release processes.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-792"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-790">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-791'
	>
	Project Architecture Documentation</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-795"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-793">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-794'
	>
	This type of documentation is a combination of process and code documentation. Having a general overview of how the whole project works is very important. Architectural changes may occur in the project’s lifecycle, and it is important that they are documented, as the context behind some decisions may also change over time.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-798"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-796">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-797'
	>
	User Documentation</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-801"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-799">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-800'
	>
	User documentation refers to documentation that is intended for end-users of a product or service. This can include user manuals, online help files, or tutorials. User documentation guides users to use the product or service effectively, reducing the risk of errors or confusion and improving overall user satisfaction.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-804"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-802">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-803'
	>
	Writing documentation saves resources</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-807"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-805">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-806'
	>
	There are multiple real-life scenarios where clear and detailed documentation saves time, and with that money. One of the most common examples is onboarding new developers to the project. With clear and detailed project documentation, new developers need less time for project onboarding, as the documentation includes not only the project setup, but also explains the processes involved.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-810"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-808">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-809'
	>
	For example, documentation for onboarding a new developer will include notes from meetings with project managers, definitions relevant for certain tasks, notes from meetings with other developers to provide technical support for project installation, release process documentation, and so on.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-812"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-811'
	>
	<strong>Documenting a release process can be crucial for a successful release of the project. Even a simple checklist of things to do can be considered good documentation as it ensures that all steps will be completed.</strong> </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-815"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-813">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-814'
	>
	For example, leaving out one step could lead to an unsuccessful release, which can cause delays and missed deadlines on the project. In this event, finding out which step was missed without documentation could be a huge headache.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-818"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-816">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-817'
	>
	Further, every project can be affected by changes in personnel, either from the side of the implementation team, or from the sides of the project owner or client. New project owners might not know the full history of the project, and the reasoning behind previous decisions. Proper documentation helps to explain why some decisions were made, and the reasons behind them.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-821"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-819">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-820'
	>
	Another example where documentation is crucial is when the developer is implementing a quick-fix or a hack in the code. Sometimes hacks are a necessary evil, solving a burning issue until a better solution is implemented. These situations must be documented so that everyone involved is aware of potential risks when removing the hack.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-824"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-822">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-823'
	>
	How to write good documentation</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-827"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-825">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-826'
	>
	Write for the audience</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-830"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-828">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-829'
	>
	When writing documentation, you first need to consider your audience. If it’s aimed at other developers, it can be more technical and detailed. However, when you’re writing for a non-technical audience such as stakeholders or end-users, documentation should be written in plain language, avoiding technical jargon. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-833"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-831">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-832'
	>
	Sometimes the audience types will overlap, and you should consider providing different levels of documentation – detailed technical documentation for developers and simpler, user-friendly documentation for other stakeholders.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-836"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-834">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-835'
	>
	Provide value and meaning</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-839"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-837">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-838'
	>
	One of the most common pitfalls when writing documentation is not providing the backstory behind an initiative. You need to explain the problem that needs solving, how it should be solved, and why you need to do it in the first place. By providing answers to these three questions, the audience will easily get onboard with the problem and its solution.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-842"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-840">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-841'
	>
	Be clear and concise</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-845"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-843">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-844'
	>
	Good documentation should be easy to understand. Use simple language and clear explanations of concepts and processes. Avoid using acronyms or abbreviations without explaining what they mean.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-848"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-846">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-847'
	>
	Additionally, be concise and stick to the main points, avoiding unnecessary details or tangents that can confuse the reader. Use the DRY principle of software development – Do Not Repeat Yourself. Provide simple and clear examples stripped of unnecessary details.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-851"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-849">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-850'
	>
	Use a consistent format</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-854"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-852">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-853'
	>
	When it comes to documentation, consistency is key. Use the same format for each section, such as headings, subheadings, bullet points, and numbering. Add a table of contents and linkable sections if possible. This makes it easier for readers to navigate the document and find the information they need quickly. Additionally, consider using templates or standardized formats to ensure consistency across different documents and projects.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-857"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-855">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-856'
	>
	Keep it up to date</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-860"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-858">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-859'
	>
	Documentation is only useful if it is accurate and up to date. Make sure to regularly review and update your documentation as changes are made to the project or code. This includes keeping track of new features or functionalities, changes to code structure or architecture, and updates to external dependencies or libraries. By keeping your documentation current, you can ensure that it remains a valuable resource for yourself and others involved in the project.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-863"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-861">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-862'
	>
	Use version control</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-866"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-864">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-865'
	>
	Just like code, documentation should be managed and version-controlled. Using a version control system can help track changes to the documentation over time. Additionally, it can facilitate collaboration among team members, allowing multiple people to work on the documentation simultaneously, merging changes as needed.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-869"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-867">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-868'
	>
	Utilizing GitHub Copilot for writing code documentation</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-872"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-870">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-871'
	>
	Code documentation is an essential component of software development, and AI can help you with it. By providing clear and detailed information about functions, variables, and other components of the code, developers can more easily understand how the software works and make changes as needed.<strong> </strong>This information is especially important for large and complex codebases, where it can be difficult to keep track of how everything fits together.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-875"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-873">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-874'
	>
	<a href="https://infinum.com/blog/ai-coding-assistants/">Using GitHub Copilot to write documentation</a> can save time and provide great results. Here is an example of documentation written by GitHub Copilot Labs Visual Studio Code extension for the <code>refreshToken</code> function.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-877"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-javascript github-light" data-language="javascript" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #6a737d;">/**</span><span class="token" style="color: #6a737d;">
</span></span><span class="line"><span class="token" style="color: #6a737d;"> * This function is used to refresh the current access token when it expires.
</span></span><span class="line"><span class="token" style="color: #6a737d;"> * If the current response is an OAuth2 protected resource URL (i.e. a URL that requires an access token to be accessed) and
</span></span><span class="line"><span class="token" style="color: #6a737d;"> * the response status is 401 (unauthorized), then a new access token is retrieved using the refresh token.
</span></span><span class="line"><span class="token" style="color: #6a737d;"> * If the current response is not an OAuth2 protected resource URL or the response status is not 401, then the response is
</span></span><span class="line"><span class="token" style="color: #6a737d;"> * returned without any changes.
</span></span><span class="line"><span class="token" style="color: #6a737d;"> * </span><span class="token" style="color: #d73a49;">@</span><span class="token" style="color: #d73a49;">param</span><span class="token" style="color: #24292e;"> reques</span><span class="token" style="color: #6a737d;">t</span><span class="token" style="color: #6a737d;"> The current request
</span></span><span class="line"><span class="token" style="color: #6a737d;"> * </span><span class="token" style="color: #d73a49;">@</span><span class="token" style="color: #d73a49;">param</span><span class="token" style="color: #24292e;"> respons</span><span class="token" style="color: #6a737d;">e</span><span class="token" style="color: #6a737d;"> The current response
</span></span><span class="line"><span class="token" style="color: #6a737d;"> * </span><span class="token" style="color: #d73a49;">@</span><span class="token" style="color: #d73a49;">returns</span><span class="token" style="color: #6a737d;"> A promise that resolves to the response
</span></span><span class="line"><span class="token" style="color: #6a737d;"> </span><span class="token" style="color: #6a737d;">*/</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #d73a49;">async</span><span class="token"> </span><span class="token" style="color: #d73a49;">function</span><span class="token"> </span><span class="token" style="color: #6f42c1;">refreshToken</span><span class="token">(</span><span class="token" style="color: #e36209;">request</span><span class="token" style="color: #d73a49;">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">Request</span><span class="token">,</span><span class="token"> </span><span class="token" style="color: #e36209;">response</span><span class="token" style="color: #d73a49;">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">Response</span><span class="token">)</span><span class="token" style="color: #d73a49;">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">Promise</span><span class="token">&lt;</span><span class="token" style="color: #6f42c1;">Response</span><span class="token">&gt;</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">...</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-880"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-878">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-879'
	>
	The function comment generated by GitHub Copilot Labs extension describes the context of the function and related parameters. The purpose and the application of the function is described in a detailed way, which provides value to the developer. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-882"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-881'
	>
	<strong>The purpose of code documentation should be to answer the question <em>why</em>, not <em>how</em> something works. Well-written and clean code should answer the <em>how</em> question by itself. </strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-885"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-883">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-884'
	>
	Despite the fact that the GitHub Copilot only deals with the “why” part, it’s a great stepping stone for kicking off documentation. For example, the provided example could be expanded with additional context on the cases when the token can expire and when this function should be called.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-888"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-886">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-887'
	>
	Common anti-patterns</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-891"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-889">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-890'
	>
	Writing good documentation can sometimes be a hard job, but it’s a lot easier if you know what to avoid. Here are some common anti-patterns you might come across.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-894"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-892">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-893'
	>
	No documentation</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-897"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-895">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-896'
	>
	This one is obvious: lack of documentation can make it difficult for everyone involved to understand how a system or a process works, which can lead to confusion, errors, and delays. It is important to have at least some level of documentation in place, even if it&#8217;s just basic notes or comments in the code.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-900"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-898">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-899'
	>
	Over-documentation</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-903"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-901">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-902'
	>
	On the other end of the spectrum, over-documentation can be just as problematic. If there is too much documentation, it can be difficult to navigate and find the information you need, which can be just as confusing and time-consuming as having no documentation at all. It is important to strike a balance between providing too little information and overwhelming the reader.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-906"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-904">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-905'
	>
	Poor organization</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-909"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-907">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-908'
	>
	Poorly organized or poorly structured documentation can be difficult to navigate and understand. It is important to use a consistent format and structure throughout the document, with clear headings, subheadings, and bullet points. Consider using tables of contents, glossaries, or indexes to help readers find the information they need quickly.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-912"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-910">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-911'
	>
	Outdated documentation</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-915"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-913">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-914'
	>
	Outdated documentation can be misleading or inaccurate, leading to errors and confusion. It is important to keep documentation up to date as changes are made to the system or process.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-918"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-916">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-917'
	>
	Writing for the wrong audience</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-921"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-919">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-920'
	>
	Documentation that is written for the wrong audience can be confusing or irrelevant. Make sure to consider who the intended audience is when writing documentation, and tailor the language and level of detail accordingly. For example, documentation intended for developers may be more technical and detailed, while documentation intended for end-users may need to be more simple and user-friendly.<br />
<br />
It&#8217;s kind of like an iOS developer wanting to learn about the DocC documentation environment and reaching this page instead. They would be much better off reading the article <a href="https://infinum.com/blog/documentation-is-the-key-to-happy-coding/">Documentation Is the Key to Happy Coding</a>, for example.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-924"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-922">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-923'
	>
	Documentation ensures you deliver quality products</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-927"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-925">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-926'
	>
	In conclusion, documentation is an essential aspect of software development. From feature documentation to process documentation and code documentation, it provides the foundation for clear communication and understanding. Good documentation also reduces the risk of misunderstandings and errors, and improves the quality and maintainability of software.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-930"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-928">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-929'
	>
	With solid documentation in place, clients can save time and money in the long run. When everything is properly documented, onboarding new developers to the project is quicker, it’s easier to manage changes in staff and monitor the release process.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-933"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-931">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-932'
	>
	To make your documentation effective, it’s important to consider the different types available, such as process documentation, code documentation and user documentation. Additionally, it is important to avoid common anti-patterns, such as lack of documentation, over-documentation, poor organization, outdated documentation, and writing for the wrong audience.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-936"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-934">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-935'
	>
	By prioritizing documentation and following best practices, developers and other stakeholders can ensure that they save time and money, avoid common problems, and create high-quality products for their clients and users.</p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/software-documentation/">Creating Software Documentation that Saves Time and Money</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
		
	</channel>
</rss>