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

<channel>
	<title>OTP - Inero Software - Software Consulting</title>
	<atom:link href="https://inero-software.com/tag/otp/feed/" rel="self" type="application/rss+xml" />
	<link>https://inero-software.com/tag/otp/</link>
	<description>We unleash innovations using cutting-edge technologies, modern design and AI</description>
	<lastBuildDate>Thu, 13 Feb 2025 11:33:00 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://inero-software.com/wp-content/uploads/2018/11/inero-logo-favicon.png</url>
	<title>OTP - Inero Software - Software Consulting</title>
	<link>https://inero-software.com/tag/otp/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">153509928</site>	<item>
		<title>Behind the Scenes #2: Implementing email-based MFA in Keycloak</title>
		<link>https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/</link>
		
		<dc:creator><![CDATA[Marceli Formela]]></dc:creator>
		<pubDate>Thu, 13 Feb 2025 09:50:32 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Company]]></category>
		<category><![CDATA[Keycloak]]></category>
		<category><![CDATA[2FA]]></category>
		<category><![CDATA[emial]]></category>
		<category><![CDATA[IAM]]></category>
		<category><![CDATA[keycloak]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[Multi-Factor Authentication]]></category>
		<category><![CDATA[OTP]]></category>
		<guid isPermaLink="false">https://inero-software.com/?p=7042</guid>

					<description><![CDATA[<p>In this post, we’ll explore a custom MFA implementation that sends a one-time authentication code to the user’s email.</p>
<p>Artykuł <a href="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/">Behind the Scenes #2: Implementing email-based MFA in Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com">Inero Software - Software Consulting</a>.</p>
]]></description>
										<content:encoded><![CDATA[		<div data-elementor-type="wp-post" data-elementor-id="7042" class="elementor elementor-7042" data-elementor-post-type="post">
				<div class="elementor-element elementor-element-091b893 e-flex e-con-boxed e-con e-parent" data-id="091b893" data-element_type="container">
					<div class="e-con-inner">
		<div class="elementor-element elementor-element-628ea76 e-con-full e-flex e-con e-child" data-id="628ea76" data-element_type="container">
				</div>
		<div class="elementor-element elementor-element-9ca8a9e e-con-full e-flex e-con e-child" data-id="9ca8a9e" data-element_type="container">
				<div class="elementor-element elementor-element-edd1fd0 elementor-widget elementor-widget-html" data-id="edd1fd0" data-element_type="widget" data-widget_type="html.default">
				<div class="elementor-widget-container">
			 		</div>
				</div>
				<div class="elementor-element elementor-element-3e56066 elementor-widget elementor-widget-text-editor" data-id="3e56066" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<h5><strong>Keycloak natively supports many secure login solutions and comes with built-in one-time password (OTP) mechanisms, such as authentication via mobile apps like Google Authenticator or our solution <a href="https://inero-software.com/introducing-authm8-a-free-cross-platform-2fa-solution-tailored-to-your-brand-for-secure-authentication/">AuthM8</a>. However, if we want to use other advanced authentication methods and for example send OTP codes via email, then similar to SMS multi factor authentication (more details <a href="https://inero-software.com/custom-sms-authenticator-with-keycloak/">HERE</a>), we need to implement this functionality ourselves. In this post, we’ll explore a custom MFA implementation that sends a one-time authentication code to the user’s email. </strong></h5>						</div>
				</div>
				<div class="elementor-element elementor-element-0994f82 elementor-widget elementor-widget-heading" data-id="0994f82" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h3 class="elementor-heading-title elementor-size-default">How does email-based MFA work?
</h3>		</div>
				</div>
				<div class="elementor-element elementor-element-8e8909e elementor-widget elementor-widget-text-editor" data-id="8e8909e" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p>The authentication process consists of two main stages:</p>						</div>
				</div>
				<div class="elementor-element elementor-element-8435873 elementor-widget elementor-widget-text-editor" data-id="8435873" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<ul><li style="list-style-type: none;"><ul><li><b style="color: var( --e-global-color-text ); text-align: var(--text-align);">Generating and sending the MFA code</b></li></ul></li></ul><p><span style="font-weight: 400;">If the user already has an active cookie confirming a previous MFA verification, they should be immediately authenticated. Otherwise, Keycloak creates a new credential for the user and generates a one-time code based on configurable parameters like length or time-to-live.  The code is stored in the user’s credentials and then is emailed using the email provider.</span></p><p> </p><ul><li style="list-style-type: none;"><ul><li aria-level="1"><b>Verifying the entered code</b></li></ul></li></ul><p><span style="font-weight: 400;">When a user submits the code, KC retrieves the stored credential and compares the entered value. If the code is correct and still valid (not expired), authentication is successful, and a cookie is set to remember the verification. If the code is incorrect, the user is prompted to re-enter it and if the code has expired, an error message is shown and the process must be restarted.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-d65460b elementor-widget elementor-widget-image" data-id="d65460b" data-element_type="widget" data-widget_type="image.default">
				<div class="elementor-widget-container">
													<img fetchpriority="high" decoding="async" data-attachment-id="7044" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/email-based-two-factor-authentication-flowchart/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="EMAIL-BASED TWO-FACTOR AUTHENTICATION FLOWCHART" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-1030x579.png" tabindex="0" role="button" width="1030" height="579" src="https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-1030x579.png" class="attachment-large size-large wp-image-7044" alt="" srcset="https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-300x169.png 300w, https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-768x432.png 768w, https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-533x300.png 533w, https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART.png 1920w" sizes="(max-width: 1030px) 100vw, 1030px" data-attachment-id="7044" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/email-based-two-factor-authentication-flowchart/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="EMAIL-BASED TWO-FACTOR AUTHENTICATION FLOWCHART" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/EMAIL-BASED-TWO-FACTOR-AUTHENTICATION-FLOWCHART-1030x579.png" role="button" />													</div>
				</div>
				<div class="elementor-element elementor-element-5925a75 elementor-widget elementor-widget-heading" data-id="5925a75" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h3 class="elementor-heading-title elementor-size-default"><strong data-start="157" data-end="185">Email MFA: Pros and Cons</strong> </h3>		</div>
				</div>
				<div class="elementor-element elementor-element-3c6c4e2 elementor-widget elementor-widget-text-editor" data-id="3c6c4e2" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">Email-based MFA offers additional security when the primary factor, such as a password, has been compromised. This is particularly helpful in cases where passwords are brute-forced or easily guessed, such as with common combinations like 123456. Similarly, this solution offers protection against credential stuffing, where attackers use leaked passwords from other breaches to attempt logging into account.</span></p><p><span style="font-weight: 400;">There are several other benefits to using email as a MFA:</span></p><ul><li style="list-style-type: none;"><ul><li aria-level="1"><span style="font-weight: 400;">Email MFA does not require users to provide additional sensitive information, such as a phone number, reducing concerns about privacy.</span></li></ul></li></ul><ul><li style="list-style-type: none;"><ul><li aria-level="1"><span style="font-weight: 400;">It does not require users to install a separate app or complete a complicated setup, which simplifies the process.</span></li></ul></li></ul><ul><li style="list-style-type: none;"><ul><li aria-level="1"><span style="font-weight: 400;"> </span><span style="font-weight: 400;">Users are accustomed to providing their email for various purposes, such as receiving important account updates or resetting passwords. This familiarity makes it more accessible.</span></li></ul></li></ul><p><span style="font-weight: 400;">However, email as a delivery channel does have some drawbacks. If an attacker compromises your email (gains access to an email account through stolen credentials or by exploiting an active session.), they could potentially reset other accounts’ passwords as well. For users in vulnerable situations, such as those with access to shared devices, email-based MFA can still leave them exposed. As with any security measure, it’s essential to weigh the benefits against the potential risks and mix email MFA with other safeguards, such as strong passwords policy and secure email practices.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-4fd89fe elementor-widget elementor-widget-heading" data-id="4fd89fe" data-element_type="widget" data-widget_type="heading.default">
				<div class="elementor-widget-container">
			<h3 class="elementor-heading-title elementor-size-default">Implementing Email MFA</h3>		</div>
				</div>
				<div class="elementor-element elementor-element-cfc16d2 elementor-widget elementor-widget-image" data-id="cfc16d2" data-element_type="widget" data-widget_type="image.default">
				<div class="elementor-widget-container">
													<img decoding="async" data-attachment-id="7045" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/zrzut-ekranu-2025-02-13-102335/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335.png" data-orig-size="755,508" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Zrzut ekranu 2025-02-13 102335" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335-300x202.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335.png" tabindex="0" role="button" width="755" height="508" src="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335.png" class="attachment-large size-large wp-image-7045" alt="" srcset="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335.png 755w, https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335-300x202.png 300w, https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335-446x300.png 446w" sizes="(max-width: 755px) 100vw, 755px" data-attachment-id="7045" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/zrzut-ekranu-2025-02-13-102335/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335.png" data-orig-size="755,508" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Zrzut ekranu 2025-02-13 102335" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335-300x202.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102335.png" role="button" />													</div>
				</div>
				<div class="elementor-element elementor-element-295f2c1 elementor-widget elementor-widget-text-editor" data-id="295f2c1" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">In this modified Browser Authentication Flow, we integrate our custom MFA as an additional authentication method. There are two new steps:</span></p><ul><li style="list-style-type: none;"><ul><li style="font-weight: 400;" aria-level="1"><b>MFA Email setup</b><span style="font-weight: 400;"> – this step ensures that email is set up and verified for the user before proceeding. If the user does not have a custom MFA Credential (which stores OTP codes as secrets), it will be set as well.</span></li></ul></li></ul>						</div>
				</div>
				<div class="elementor-element elementor-element-1cb9fd2 elementor-widget elementor-widget-text-editor" data-id="1cb9fd2" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<pre><span style="font-weight: 400;">public class MfaEmailSetupAuthenticator implements Authenticator, CredentialValidator&lt;MfaEmailCredentialProvider&gt; {</span><br /><span style="font-weight: 400;">@Override</span><br /><span style="font-weight: 400;">public void authenticate(AuthenticationFlowContext context) {</span><br /><span style="font-weight: 400;">[…]</span><br /><span style="font-weight: 400;">// Require email verification</span><br /><span style="font-weight: 400;">if (!userModel.isEmailVerified()) {</span><br /><span style="font-weight: 400;">userModel.addRequiredAction(UserModel.RequiredAction.VERIFY_EMAIL);</span><br /><span style="font-weight: 400;">}</span><br /><span style="font-weight: 400;">// Add MFA email credential if not present</span><br /><span style="font-weight: 400;">if (!getCredentialProvider(context.getSession()).isConfiguredFor(realmModel, userModel, MfaEmailCredentialModel.TYPE)) {</span><br /><span style="font-weight: 400;">userModel.credentialManager().createStoredCredential(new MfaEmailCredentialModel(new MfaEmailCredentialData()));</span><br /><span style="font-weight: 400;">}</span><br /><span style="font-weight: 400;">[…]</span></pre>						</div>
				</div>
				<div class="elementor-element elementor-element-6908dab elementor-widget__width-initial elementor-widget elementor-widget-image" data-id="6908dab" data-element_type="widget" data-widget_type="image.default">
				<div class="elementor-widget-container">
													<img decoding="async" data-attachment-id="7046" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/zrzut-ekranu-2025-02-13-102520/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520.png" data-orig-size="635,398" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Zrzut ekranu 2025-02-13 102520" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520-300x188.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520.png" tabindex="0" role="button" width="635" height="398" src="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520.png" class="attachment-large size-large wp-image-7046" alt="" srcset="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520.png 635w, https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520-300x188.png 300w, https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520-479x300.png 479w" sizes="(max-width: 635px) 100vw, 635px" data-attachment-id="7046" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/zrzut-ekranu-2025-02-13-102520/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520.png" data-orig-size="635,398" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Zrzut ekranu 2025-02-13 102520" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520-300x188.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102520.png" role="button" />													</div>
				</div>
				<div class="elementor-element elementor-element-eafd6c8 elementor-widget elementor-widget-text-editor" data-id="eafd6c8" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<ul><li style="list-style-type: none;"><ul><li style="font-weight: 400;" aria-level="1"><b>MFA Email Authentication</b><span style="font-weight: 400;"> – this is the actual authentication step where a one-time code is sent via email. Marked as Alternative, meaning it can be used instead of other MFA methods like mobile app OTP.</span></li></ul></li></ul><p><span style="font-weight: 400;">Here, you can see how the configuration of this authenticator could look like in the Keycloak authentication flow.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-bdcf27f elementor-widget__width-initial elementor-widget elementor-widget-image" data-id="bdcf27f" data-element_type="widget" data-widget_type="image.default">
				<div class="elementor-widget-container">
													<img loading="lazy" decoding="async" data-attachment-id="7047" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/zrzut-ekranu-2025-02-13-102652/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652.png" data-orig-size="473,622" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Zrzut ekranu 2025-02-13 102652" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652-228x300.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652.png" tabindex="0" role="button" width="473" height="622" src="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652.png" class="attachment-large size-large wp-image-7047" alt="" srcset="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652.png 473w, https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652-228x300.png 228w" sizes="(max-width: 473px) 100vw, 473px" data-attachment-id="7047" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/zrzut-ekranu-2025-02-13-102652/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652.png" data-orig-size="473,622" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Zrzut ekranu 2025-02-13 102652" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652-228x300.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-102652.png" role="button" />													</div>
				</div>
				<div class="elementor-element elementor-element-10b800d elementor-widget elementor-widget-text-editor" data-id="10b800d" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<ul><li style="list-style-type: none;"><ul><li style="font-weight: 400;" aria-level="1"><b>Max Cookie Age</b><span style="font-weight: 400;"> this setting determines how long the MFA session (cookie) is valid. If the cookie is still valid, the user won&#8217;t be prompted for MFA. </span></li><li style="font-weight: 400;" aria-level="1"><b>Time-to-live</b><span style="font-weight: 400;"> indicates the lifetime of the MFA code.</span></li></ul></li></ul><p> </p><p><span style="font-weight: 400;">Now let’s take a look at the code. </span></p><p> </p><p><span style="font-weight: 400;">The method below handles the MFA process itself. If a valid cookie exists (indicating that the user has already completed MFA), the method immediately returns success, meaning the authentication flow is complete without requiring additional actions.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-89fa524 elementor-widget elementor-widget-text-editor" data-id="89fa524" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<pre><span style="font-weight: 400;">@Override</span><br /><span style="font-weight: 400;">public void authenticate(AuthenticationFlowContext context) {</span><br /><span style="font-weight: 400;">if (hasValidCookie(context)) {</span><br /><span style="font-weight: 400;">context.success();</span><br /><span style="font-weight: 400;">return;</span><br /><span style="font-weight: 400;">}</span><br /><span style="font-weight: 400;">[…]</span></pre>						</div>
				</div>
				<div class="elementor-element elementor-element-55ff859 elementor-widget elementor-widget-text-editor" data-id="55ff859" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">If there is no cookie, we should try to retrieve the user’s existing MFA credential from the credential provider. If the user doesn’t have one, a new instance is created using the MfaEmailCredentialModel which just extends the built-in CredentialModel:</span><span style="font-weight: 400;"><br /></span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-0af0624 elementor-widget elementor-widget-text-editor" data-id="0af0624" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<pre><span style="font-weight: 400;">[…]</span><br /><span style="font-weight: 400;">// get existing credential or create a new one</span><br /><span style="font-weight: 400;">CredentialModel credentialModel = getCredentialProvider(session)</span><br /><span style="font-weight: 400;">.getDefaultCredential(session, context.getRealm(), user);</span><br /><span style="font-weight: 400;">if (credentialModel == null) {</span><br /><span style="font-weight: 400;">credentialModel = user.credentialManager().createStoredCredential(new MfaEmailCredentialModel(new MfaEmailCredentialData()));</span><br /><span style="font-weight: 400;">}</span><br /><span style="font-weight: 400;">[…]</span></pre>						</div>
				</div>
				<div class="elementor-element elementor-element-c7af14a elementor-widget elementor-widget-text-editor" data-id="c7af14a" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">Then the authenticate method reads configuration properties like code length and TTL (time-to-live). The code itself can be generated using some utils method and will be stored as the secretData in the credential model.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-6551375 elementor-widget elementor-widget-text-editor" data-id="6551375" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<pre><span style="font-weight: 400;">// generate and store code</span><br /><span style="font-weight: 400;">int length = Integer.parseInt(configMap.get(CONFIG_CODE_LENGTH));</span><br /><span style="font-weight: 400;">int ttl = Integer.parseInt(configMap.get(CONFIG_CODE_TTL));</span><br /><span style="font-weight: 400;">String code = MfaEmailCodesUtils.generateCode(length);</span><br /><span style="font-weight: 400;">credentialModel.setSecretData(code);</span><br /><span style="font-weight: 400;">user.credentialManager().updateStoredCredential(credentialModel);</span><br /><span style="font-weight: 400;">AuthenticationSessionModel authSession = context.getAuthenticationSession();</span><br /><span style="font-weight: 400;">authSession.setAuthNote("ttl", Long.toString(System.currentTimeMillis() + (ttl * 1000L)));</span></pre>						</div>
				</div>
				<div class="elementor-element elementor-element-b7f4d62 elementor-widget elementor-widget-text-editor" data-id="b7f4d62" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">In the end the sendCode method is called to send the generated code to the user’s email. If the email is sent successfully, the method presents the form where the user can enter the MFA code.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-dc63501 elementor-widget elementor-widget-text-editor" data-id="dc63501" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<pre><span style="font-weight: 400;">// send email and show input form</span><br /><span style="font-weight: 400;">try {</span><br /><span style="font-weight: 400;">MfaEmailCodesUtils.sendCode(session, user, ttl, code, configMap);</span><br /><span style="font-weight: 400;">context.challenge(context.form().setAttribute("realm", context.getRealm()).createForm(TPL_CODE));</span><br /><span style="font-weight: 400;">} catch (Exception e) {</span><br /><span style="font-weight: 400;">context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR,</span><br /><span style="font-weight: 400;">context.form().setError("mfaEmailNotSent", e.getMessage())  .createErrorPage(Response.Status.INTERNAL_SERVER_ERROR));</span><br /><span style="font-weight: 400;">}</span></pre>						</div>
				</div>
				<div class="elementor-element elementor-element-b41ad7b elementor-widget elementor-widget-text-editor" data-id="b41ad7b" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">The second major part of our Authenticator is the action method which handles the validation of the code entered by the user. It is invoked when the user submits the input form after receiving the email.  </span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-9551f9f elementor-widget__width-initial elementor-widget elementor-widget-image" data-id="9551f9f" data-element_type="widget" data-widget_type="image.default">
				<div class="elementor-widget-container">
													<img loading="lazy" decoding="async" data-attachment-id="7048" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/zrzut-ekranu-2025-02-13-103114/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114.png" data-orig-size="663,391" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Zrzut ekranu 2025-02-13 103114" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114-300x177.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114.png" tabindex="0" role="button" width="663" height="391" src="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114.png" class="attachment-large size-large wp-image-7048" alt="" srcset="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114.png 663w, https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114-300x177.png 300w, https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114-509x300.png 509w" sizes="(max-width: 663px) 100vw, 663px" data-attachment-id="7048" data-permalink="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/zrzut-ekranu-2025-02-13-103114/" data-orig-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114.png" data-orig-size="663,391" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="Zrzut ekranu 2025-02-13 103114" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114-300x177.png" data-large-file="https://inero-software.com/wp-content/uploads/2025/02/Zrzut-ekranu-2025-02-13-103114.png" role="button" />													</div>
				</div>
				<div class="elementor-element elementor-element-460cfb7 elementor-widget elementor-widget-text-editor" data-id="460cfb7" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">The method retrieves the user’s credential from the provider and then the code is validated by checking it against the stored credential using the custom isValid method.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-ff19a7e elementor-widget elementor-widget-text-editor" data-id="ff19a7e" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<pre><span style="font-weight: 400;">[…]</span><br /><span style="font-weight: 400;">final MfaEmailCredentialModel credentialModel = getCredentialProvider(session)</span><br /><span style="font-weight: 400;">        .getDefaultCredential(session, context.getRealm(), user);</span><br /><span style="font-weight: 400;">boolean isValid = getCredentialProvider(session).isValid(context.getRealm(), user,</span><br /><span style="font-weight: 400;">    </span> <span style="font-weight: 400;">new UserCredentialModel(credentialModel.getId(), getCredentialProvider(context.getSession()).getType(), enteredCode));</span><br /><span style="font-weight: 400;">[…]</span></pre>						</div>
				</div>
				<div class="elementor-element elementor-element-7b502f8 elementor-widget elementor-widget-text-editor" data-id="7b502f8" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">If the code is valid, the next step is to check if it is expired. We can also set a cookie that stores the MFA session to prevent the user from </span><b>being prompted for MFA again</b><span style="font-weight: 400;"> during the cookie’s validity period.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-3db7437 elementor-widget elementor-widget-text-editor" data-id="3db7437" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<pre><span style="font-weight: 400;">[…]</span><br /><span style="font-weight: 400;">// valid</span><br /><span style="font-weight: 400;">HttpResponse response = context.getSession().getContext().getHttpResponse();</span><br /><span style="font-weight: 400;">response.setCookieIfAbsent(createCookie(context));</span><br /><span style="font-weight: 400;">context.success();</span><br /><span style="font-weight: 400;">[…]</span></pre><p><span style="font-weight: 400;"> </span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-83cf638 elementor-widget elementor-widget-text-editor" data-id="83cf638" data-element_type="widget" data-widget_type="text-editor.default">
				<div class="elementor-widget-container">
							<p><span style="font-weight: 400;">Of course, in this post, we will not cover the entire topic, omitting implementation details such as sending the code, generating the code, validation, and creating our custom cookie.</span></p><p><span style="font-weight: 400;"><br></span></p>
<p><span style="font-weight: 400;">However, we have walked through the major steps of implementing 2FA using email-based codes. On the one hand, this approach offers a simple and accessible solution. Although it has its drawbacks, using it in solutions like Keycloak helps mitigate many of these vulnerabilities. Keycloak also provides the flexibility to combine email-based MFA with other security measures, creating a more layered and resilient authentication process that can help protect against evolving cybersecurity threats.</span></p>						</div>
				</div>
				<div class="elementor-element elementor-element-a27180c elementor-cta--skin-cover elementor-animated-content elementor-bg-transform elementor-bg-transform-zoom-in elementor-widget elementor-widget-call-to-action" data-id="a27180c" data-element_type="widget" data-widget_type="call-to-action.default">
				<div class="elementor-widget-container">
					<div class="elementor-cta">
					<div class="elementor-cta__bg-wrapper">
				<div class="elementor-cta__bg elementor-bg" style="background-image: url(https://inero-software.com/wp-content/uploads/2024/11/tlo-popup-keycloak-1030x731.png);" role="img" aria-label="tło popup keycloak"></div>
				<div class="elementor-cta__bg-overlay"></div>
			</div>
							<div class="elementor-cta__content">
				
									<h3 class="elementor-cta__title elementor-cta__content-item elementor-content-item elementor-animated-item--grow">
						Do you need help configuring multi-factor authentication?					</h3>
				
									<div class="elementor-cta__description elementor-cta__content-item elementor-content-item elementor-animated-item--grow">
						Schedule a meeting to find out how we can help you.					</div>
				
									<div class="elementor-cta__button-wrapper elementor-cta__content-item elementor-content-item elementor-animated-item--grow">
					<a class="elementor-cta__button elementor-button elementor-size-" href="https://calendar.google.com/calendar/u/0/appointments/schedules/AcZssZ3e3C_1YeBkt1uCr_qfOnG_N298UgLFwORcSTXigrPfOk0ls3ok-Uw_dSeGCoLdtYsN13GMm-n-">
						Schedule a meeting					</a>
					</div>
							</div>
						</div>
				</div>
				</div>
				</div>
		<div class="elementor-element elementor-element-6bc7752 e-con-full e-flex e-con e-child" data-id="6bc7752" data-element_type="container">
				</div>
					</div>
				</div>
		<div class="elementor-element elementor-element-091ddaf e-flex e-con-boxed e-con e-parent" data-id="091ddaf" data-element_type="container">
					<div class="e-con-inner">
					</div>
				</div>
				</div>
		<p>Artykuł <a href="https://inero-software.com/behind-the-scenes-2-implementing-email-based-mfa-in-keycloak/">Behind the Scenes #2: Implementing email-based MFA in Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com">Inero Software - Software Consulting</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">7042</post-id>	</item>
		<item>
		<title>Behind the Scenes: Custom SMS Authenticator with Keycloak</title>
		<link>https://inero-software.com/custom-sms-authenticator-with-keycloak/</link>
		
		<dc:creator><![CDATA[Marceli Formela]]></dc:creator>
		<pubDate>Fri, 14 Jun 2024 10:29:52 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Company]]></category>
		<category><![CDATA[Keycloak]]></category>
		<category><![CDATA[keycloak]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[OTP]]></category>
		<category><![CDATA[realm]]></category>
		<category><![CDATA[sms]]></category>
		<category><![CDATA[sms gateway]]></category>
		<category><![CDATA[SMS-based OTP]]></category>
		<category><![CDATA[SPI]]></category>
		<guid isPermaLink="false">https://inero-software.com/?p=5838</guid>

					<description><![CDATA[<p>Artykuł <a href="https://inero-software.com/custom-sms-authenticator-with-keycloak/">Behind the Scenes: Custom SMS Authenticator with Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com">Inero Software - Software Consulting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="row"><div class="col-sm-1"></div><div class="col-sm-10">
<p>&nbsp;</p>
<p><span style="font-weight: 400;">With the increasing number of cyber threats, multi-factor authentication (MFA) has become a standard security measure. MFA enhances protection by requiring users to verify their identity through multiple methods. In today’s digital world MFA has become a standard security practice, adding an extra layer of protection by requiring users to provide multiple forms of verification. </span></p>
<p><span style="font-weight: 400;">Among the various advanced authentication methods, SMS-based authentication stands out for its balance of security and user convenience. However, sometimes creating a custom SMS authenticator within an identity provider like Keycloak can be a complex and nuanced process, demanding an understanding of its architecture and extensibility.</span></p>
<p>&nbsp;</p>
<h3><b>Service Provider Interface (SPI)</b></h3>
<p><span style="font-weight: 400;">Keycloak aims to address the majority of use cases without forcing you to create custom code. However, it also offers flexibility for customization. To support this, Keycloak provides several SPIs that allow you to implement your own solutions. We are going to implement an authenticator that requires a valid SMS code. To create this feature, we must at least implement the org.keycloak.authentication.AuthenticatorFactory and Authenticator interfaces. The <em>AuthenticatorFactory</em> is responsible for creating instances of an Authenticator. They both extend a more generic Provider and <em>ProviderFactory</em> set of interfaces that other Keycloak components do.</span></p>
<p>&nbsp;</p>
<h3><b>Packaging classes</b></h3>
<p><span style="font-weight: 400;">We will package our classes within a single project. It must contain a file named org.keycloak.authentication.AuthenticatorFactory and must be contained in the META-INF/services/ directory. This file must list the fully qualified class name of each <em>AuthenticatorFactory</em> implementation you have in the jar. For example:</span></p>
<pre><span style="font-weight: 400;">pl.inero.keycloakext.authenticator.sms.SmsAuthenticatorFactory</span>
<span style="font-weight: 400;">pl.inero.keycloakext.authenticator.custom.CustomUsernamePasswordFormFactory</span>
<span style="font-weight: 400;">pl.inero.keycloakext.authenticator.custom.CustomCookieAuthenticatorFactory</span></pre>
<p><span style="font-weight: 400;">This services/ file is used by Keycloak to scan the providers it has to load into the system.</span></p>
<p>&nbsp;</p>
<h3><b>CredentialModel and CredentialProvider</b></h3>
<p><span style="font-weight: 400;">The first step is to configure our Credential related classes since the user&#8217;s phone number should be stored as credential record. As you can see below, the <em>Sms2faCredentialData</em> class is a straightforward data container for storing the phone number associated with the user.</span></p>
<pre><span style="font-weight: 400;">public class </span><span style="font-weight: 400;">Sms2faCredentialData {
</span>
<span style="font-weight: 400;">private </span><span style="font-weight: 400;">String phoneNumber</span><span style="font-weight: 400;">;
</span>
<span style="font-weight: 400;">@SuppressWarnings(</span><span style="font-weight: 400;">"unused"</span><span style="font-weight: 400;">) </span><span style="font-weight: 400;">//used for credentials deserialization</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">Sms2faCredentialData() {</span>
<span style="font-weight: 400;">}
</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">Sms2faCredentialData(String phoneNumber) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">this</span><span style="font-weight: 400;">.phoneNumber = phoneNumber</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}
</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getPhoneNumber() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">phoneNumber</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}
</span>
<span style="font-weight: 400;">@SuppressWarnings(</span><span style="font-weight: 400;">"unused"</span><span style="font-weight: 400;">) </span><span style="font-weight: 400;">//used for credentials deserialization</span>
<span style="font-weight: 400;">public void </span><span style="font-weight: 400;">setPhoneNumber(String phoneNumber) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">this</span><span style="font-weight: 400;">.phoneNumber = phoneNumber</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<p><span style="font-weight: 400;">The next step is to extend the <em>CredentialModel</em> class that can generate the correct format of the credential in the database. For the <em>Sms2faCredentialModel</em> objects to be fully functional, they need to encompass not only the raw JSON data inherited from their parent but also encapsulate the unmarshalled objects within their own attributes. This ensures wide accessibility and utilization of the credentials, providing easy integration and handling of authentication processes.</span></p>
<pre><span style="font-weight: 400;">public class </span><span style="font-weight: 400;">Sms2faCredentialModel </span><span style="font-weight: 400;">extends </span><span style="font-weight: 400;">CredentialModel {
</span>
<span style="font-weight: 400;">public static final </span><span style="font-weight: 400;">String TYPE = </span><span style="font-weight: 400;">"sms2fa"</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">private </span><span style="font-weight: 400;">Sms2faCredentialData smsCredentials</span><span style="font-weight: 400;">;
</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">Sms2faCredentialModel(Sms2faCredentialData smsCredentials) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">try </span><span style="font-weight: 400;">{</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">this</span><span style="font-weight: 400;">.smsCredentials = smsCredentials</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">setCredentialData(JsonSerialization.writeValueAsString(smsCredentials))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">setUserLabel(</span><span style="font-weight: 400;">"tel: " </span><span style="font-weight: 400;">+ smsCredentials.getPhoneNumber())</span><span style="font-weight: 400;">;
</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">setType(TYPE)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">} </span><span style="font-weight: 400;">catch </span><span style="font-weight: 400;">(IOException e) {</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">throw new </span><span style="font-weight: 400;">RuntimeException(e)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}
</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getPhoneNumber() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">smsCredentials.getPhoneNumber()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<p><span style="font-weight: 400;">Similar to other providers within Keycloak, the creation of the <em>CredentialProvider</em> requires the presence of a corresponding <em>CredentialsProviderFactory</em>. In meeting this requirement, we implement the <em>Sms2faCredentialProviderFactory</em> requirement, we implement the<em> Sms2faCredentialProviderFactor</em></span></p>
<pre><span style="font-weight: 400;">public class </span><span style="font-weight: 400;">Sms2faCredentialProviderFactory  </span><span style="font-weight: 400;">implements 
</span><span style="font-weight: 400;">CredentialProviderFactory&lt;Sms2faCredentialProvider&gt; {</span>

<span style="font-weight: 400;">public static final </span><span style="font-weight: 400;">String PROVIDER_ID = </span><span style="font-weight: 400;">"keycloak-ext-sms2fa"</span><span style="font-weight: 400;">;</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getId() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">PROVIDER_ID</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">CredentialProvider&lt;Sms2faCredentialModel&gt; create(KeycloakSession session) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return new </span><span style="font-weight: 400;">Sms2faCredentialProvider(session)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<p><span style="font-weight: 400;">The <em>CredentialProvider</em> interface is structured with a generic parameter that extends a <em>CredentialModel</em>, ensuring compatibility across a spectrum of credential types. Additionally, we need to implement the <em>CredentialInputValidator</em> interface, indicating to Keycloak that this provider is equipped to authenticate credentials for our custom Authenticator. Although we won’t dive into the full architecture here, Keycloak documentation covers additional methods.</span></p>
<p><span style="font-weight: 400;">Our implementation includes functionalities to create and delete credentials. These functionalities use the credential manager, responsible for the storage and retrieval of credentials, whether they’re stored locally or within federated storage systems.</span></p>
<pre><span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">CredentialModel createCredential(RealmModel realm</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">UserModel user</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">Sms2faCredentialModel credentialModel) {</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(credentialModel.getCreatedDate() == </span><span style="font-weight: 400;">null</span><span style="font-weight: 400;">) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">credentialModel.setCreatedDate(Time.currentTimeMillis())</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">return </span><span style="font-weight: 400;">user.credentialManager().createStoredCredential(credentialModel)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public boolean </span><span style="font-weight: 400;">deleteCredential(RealmModel realm</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">UserModel user</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">String credentialId) {</span>
<span style="font-weight: 400;">logger.debugv(</span><span style="font-weight: 400;">"Delete Sms2fa credential. username = {0}, credentialId = {1}"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">user.getUsername()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">credentialId)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">return </span><span style="font-weight: 400;">user.credentialManager().removeStoredCredentialById(credentialId)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span></pre>
<p><span style="font-weight: 400;">For the <em>CredentialInputValidator,</em> the main method to implement is the isValid, which tests whether a credential is valid for a given user in a given realm. This is the method that is called by the Authenticator when it seeks to validate the user’s input.</span></p>
<pre><span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public boolean </span><span style="font-weight: 400;">isValid(RealmModel realm</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">UserModel user</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">CredentialInput credentialInput) {</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">Sms2faCredentialModel credentialModel = getDefaultCredential(session</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">realm</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">user)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">String secretData = credentialModel.getSecretData()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">return </span><span style="font-weight: 400;">secretData != </span><span style="font-weight: 400;">null </span><span style="font-weight: 400;">&amp;&amp; secretData.equals(credentialInput.getChallengeResponse())</span><span style="font-weight: 400;">;</span></pre>
<p><span style="font-weight: 400;">Now we should have everything to be able to move on to implementing the authenticator itself.</span></p>
<p>&nbsp;</p>
<h3><b>AuthenticatorFactory and Authenticator</b></h3>
<p><span style="font-weight: 400;">The SmsAuthenticatorFactory class encapsulated the logic needed to configure and create instances of the SmsAuthenticator, which performs SMS-based OTP validation. It supports customization through several configurable properties.</span></p>
<pre><span style="font-weight: 400;">supports customization through several configurable properties.</span>
<span style="font-weight: 400;">public class </span><span style="font-weight: 400;">SmsAuthenticatorFactory </span><span style="font-weight: 400;">implements </span><span style="font-weight: 400;">AuthenticatorFactory {</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getId() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">"sms-authenticator"</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getDisplayType() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">"SMS Authentication"</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public </span><span style="font-weight: 400;">String getHelpText() {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return </span><span style="font-weight: 400;">"Validates an OTP sent via SMS to the users mobile phone."</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">(…)</span>
<span style="font-weight: 400;">}</span></pre>
<p><span style="font-weight: 400;">Now let’s dive into the Authenticator itself. The primary method to focus is sendChallenge(). When the flow is initially triggered, this method is invoked. It’s important to notice that it doesn’t handle the processing of the SMS code form. Rather, its role is to either render the page or continue the flow.</span></p>
<p><span style="font-weight: 400;">The HTML page requesting the received code is presented to the user, who then inputs the code and submits it. Then an HTTP request is sent to the flow via the action URL specified in the HTML form. This triggers the action() method within our Authenticator implementation. If the provided code is invalid, we reconstruct the HTML Form, appending an error message. Following this, we utilize failureChallenge(), passing the reason for the failure. It operated similarly to challenge(), but additionally logs the error, helping to detect any attack possibility.</span></p>
<pre><span style="font-weight: 400;">private void </span><span style="font-weight: 400;">sendChallenge(AuthenticationFlowContext context) {</span>
<span style="font-weight: 400;">(…)</span>
<span style="font-weight: 400;">credentialModel.setSecretData(code)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">user.credentialManager().updateStoredCredential(credentialModel)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">AuthenticationSessionModel authSession = context.getAuthenticationSession()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">authSession.setAuthNote(</span><span style="font-weight: 400;">"ttl"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">Long.toString(System.currentTimeMillis() + (ttl * </span><span style="font-weight: 400;">1000L</span><span style="font-weight: 400;">)))</span><span style="font-weight: 400;">;</span>

<span style="font-weight: 400;">try </span><span style="font-weight: 400;">{</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">/* sending SMS */</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">context.challenge(context.form().setAttribute(</span><span style="font-weight: 400;">"realm"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">context.getRealm()).createForm(TPL_CODE))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">} </span><span style="font-weight: 400;">catch </span><span style="font-weight: 400;">(Exception e) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">context.failureChallenge(AuthenticationFlowError.INTERNAL_ERROR</span><span style="font-weight: 400;">,</span>
<span style="font-weight: 400;">            </span> <span style="font-weight: 400;">context.form().setError(</span><span style="font-weight: 400;">"smsAuthSmsNotSent"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">e.getMessage())</span>
<span style="font-weight: 400;">                    </span> <span style="font-weight: 400;">.createErrorPage(Response.Status.INTERNAL_SERVER_ERROR))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span>

<span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public void </span><span style="font-weight: 400;">action(AuthenticationFlowContext context) {</span>
<span style="font-weight: 400;">(…)</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">Sms2faCredentialModel credentialModel = getCredentialProvider(session).getDefaultCredential(session</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">context.getRealm()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">user)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">boolean </span><span style="font-weight: 400;">isValid = getCredentialProvider(context.getSession()).isValid(context.getRealm()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">context.getUser()</span><span style="font-weight: 400;">,</span>
<span style="font-weight: 400;">       new </span><span style="font-weight: 400;">UserCredentialModel(credentialModel.getId()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">getCredentialProvider(context.getSession()).getType()</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">enteredCode))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(isValid) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">if </span><span style="font-weight: 400;">(Long.parseLong(ttl) &lt; System.currentTimeMillis()) {</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">// expired</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">context.failureChallenge(AuthenticationFlowError.EXPIRED_CODE</span><span style="font-weight: 400;">,
</span>
<span style="font-weight: 400;">                </span> <span style="font-weight: 400;">context.form().setError(</span><span style="font-weight: 400;">"smsAuthCodeExpired"</span><span style="font-weight: 400;">).createErrorPage(Response.Status.BAD_REQUEST))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">} </span><span style="font-weight: 400;">else </span><span style="font-weight: 400;">{</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">// valid</span>
<span style="font-weight: 400;">        </span> <span style="font-weight: 400;">context.success()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">} </span><span style="font-weight: 400;">else </span><span style="font-weight: 400;">{</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">context.getEvent().user(user).error(Errors.INVALID_USER_CREDENTIALS)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS</span><span style="font-weight: 400;">,</span>
<span style="font-weight: 400;">            </span> <span style="font-weight: 400;">context.form().setAttribute(</span><span style="font-weight: 400;">"realm"</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">context.getRealm())</span>
<span style="font-weight: 400;">                    </span> <span style="font-weight: 400;">.setError(</span><span style="font-weight: 400;">"smsAuthCodeInvalid"</span><span style="font-weight: 400;">).createForm(TPL_CODE))</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<h3></h3>
<h3><b>Authentication Flow</b></h3>
<p><span style="font-weight: 400;">To add an Authenticator into a flow, administrators must navigate to the Console. By accessing the Authentication section and navigating to the Flow tabs, they should see existing flows. Built-in flows cannot be directly modified, so to integrate the newly created Authenticator, we need to either duplicate an existing flow or create a new one from scratch.</span></p>
<p><img loading="lazy" decoding="async" data-attachment-id="5841" data-permalink="https://inero-software.com/custom-sms-authenticator-with-keycloak/2-7/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/2-3.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="2" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/2-3-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/2-3-1030x579.png" tabindex="0" role="button" class="aligncenter wp-image-5841 size-full" src="https://inero-software.com/wp-content/uploads/2024/06/2-3.png" alt="" width="1920" height="1080" srcset="https://inero-software.com/wp-content/uploads/2024/06/2-3.png 1920w, https://inero-software.com/wp-content/uploads/2024/06/2-3-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/2-3-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/2-3-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/2-3-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/2-3-533x300.png 533w" sizes="(max-width: 1920px) 100vw, 1920px" /></p>
<h3><b>Required actions</b></h3>
<p><span style="font-weight: 400;">If the phone is not set up, we should trigger a custom required action. Again, we should add the fully qualified class name to the META-INF/services directory and implement <em>RequiredActionProvider</em> interface. <em>Method requiredActionChallenge()</em> is responsible for rendering the HTML that will drive the required action.</span></p>
<pre><span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public void </span><span style="font-weight: 400;">requiredActionChallenge(RequiredActionContext context) {</span>
<span style="font-weight: 400;">LoginFormsProvider form = context.form()</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(getSmsAuthenticatorConfig(context) == </span><span style="font-weight: 400;">null</span><span style="font-weight: 400;">) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">form.setError(</span><span style="font-weight: 400;">"smsAuthMissingAuthenticatorConfig"</span><span style="font-weight: 400;">)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">Response response = form.createForm(</span><span style="font-weight: 400;">"sms-2fa-register.ftl"</span><span style="font-weight: 400;">)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">context.challenge(response)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">}</span></pre>
<p><span style="font-weight: 400;">This part is responsible for processing input from the HTML form of the required action. After entering the received SMS code, the phone number should be saved in the database as a credential. The next time we log in, we will be able to use this form of OTP.</span></p>
<pre><span style="font-weight: 400;">@Override</span>
<span style="font-weight: 400;">public void </span><span style="font-weight: 400;">processAction(RequiredActionContext context) {</span>
<span style="font-weight: 400;">    (…)</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">String phoneNumber = params.getFirst(</span><span style="font-weight: 400;">"phoneNumber"</span><span style="font-weight: 400;">)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">final </span><span style="font-weight: 400;">String code = params.getFirst(</span><span style="font-weight: 400;">"code"</span><span style="font-weight: 400;">)</span><span style="font-weight: 400;">;</span>

<span style="font-weight: 400;">if</span><span style="font-weight: 400;">(StringUtils.isBlank(phoneNumber) &amp;&amp; StringUtils.isBlank(authSession.getAuthNote(</span><span style="font-weight: 400;">"phone_number"</span><span style="font-weight: 400;">))) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">//if no phone number is set, redirect to the first page</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">requiredActionChallenge(context)</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(phoneNumber != </span><span style="font-weight: 400;">null</span><span style="font-weight: 400;">) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">sendPhoneNumberVerificationChallenge(context</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">authSession</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">phoneNumber</span><span style="font-weight: 400;">, </span><span style="font-weight: 400;">smsAuthenticatorConfig.getConfig())</span><span style="font-weight: 400;">;</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">return;</span>
<span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">if </span><span style="font-weight: 400;">(code != </span><span style="font-weight: 400;">null</span><span style="font-weight: 400;">) {</span>
<span style="font-weight: 400;">    </span> <span style="font-weight: 400;">/* verify provided SMS code */</span> 
<span style="font-weight: 400;">    </span><span style="font-weight: 400;">}</span>
<span style="font-weight: 400;">}</span></pre>
<p><span style="font-weight: 400;">The final thing you have to do is go into the Admin Console and Required Actions tab. Your new action should now be displayed and enabled in the required actions list.</span></p>
<p><img loading="lazy" decoding="async" data-attachment-id="5840" data-permalink="https://inero-software.com/custom-sms-authenticator-with-keycloak/1-6/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/1-3.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="1" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/1-3-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/1-3-1030x579.png" tabindex="0" role="button" class="wp-image-5840 aligncenter" src="https://inero-software.com/wp-content/uploads/2024/06/1-3-300x169.png" alt="" width="774" height="436" srcset="https://inero-software.com/wp-content/uploads/2024/06/1-3-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/1-3-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/1-3-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/1-3-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/1-3-533x300.png 533w, https://inero-software.com/wp-content/uploads/2024/06/1-3.png 1920w" sizes="(max-width: 774px) 100vw, 774px" /></p>
<p><span style="font-weight: 400;">If the user hasn’t provided a phone number before and SMS authenticator is set up as required in the Authentication Flow, a new view should appear.</span></p>
<p><img loading="lazy" decoding="async" data-attachment-id="5842" data-permalink="https://inero-software.com/custom-sms-authenticator-with-keycloak/3-5/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/3-3.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="3" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/3-3-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/3-3-1030x579.png" tabindex="0" role="button" class="alignnone wp-image-5842 size-full" src="https://inero-software.com/wp-content/uploads/2024/06/3-3.png" alt="" width="1920" height="1080" srcset="https://inero-software.com/wp-content/uploads/2024/06/3-3.png 1920w, https://inero-software.com/wp-content/uploads/2024/06/3-3-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/3-3-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/3-3-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/3-3-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/3-3-533x300.png 533w" sizes="(max-width: 1920px) 100vw, 1920px" /></p>
<p><span style="font-weight: 400;">In summary. configuring SMS MFA involves setting up custom required actions and authenticator providers, but of course, there are other things to cover like Keycloak &lt;-&gt; SMS gateway communication. What we looked at in this post is of course only part of the possible configuration, but the most important and Keycloak-specific one.</span></p>
<p><span style="font-weight: 400;">One of the significant advantages of SMS MFA implementation is its widespread accessibility, as most users already have mobile phones capable of receiving SMS messages. Additionally, it provides a straightforward user experience, requiring minimal setup and familiarity for users. However, this mechanism does have its drawbacks, including potential vulnerabilities such as SIM swapping attacks or interception of SMS codes. Moreover, SMS delivery can sometimes be unreliable, leading to delays or failed delivery, impacting the user experience. We should be aware of this before we decide on such a method, especially in the era of newer solutions like mobile-app OTP.</span></p>
<p><a href="https://inero-software.com/best-keycloak-practices/"><img loading="lazy" decoding="async" data-attachment-id="5833" data-permalink="https://inero-software.com/multi-factor-authentication-in-keycloak/4-4/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/4-2.png" data-orig-size="1200,100" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="4" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/4-2-300x25.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/4-2-1030x86.png" tabindex="0" role="button" class="alignnone wp-image-5833 size-full" src="https://inero-software.com/wp-content/uploads/2024/06/4-2.png" alt="" width="1200" height="100" srcset="https://inero-software.com/wp-content/uploads/2024/06/4-2.png 1200w, https://inero-software.com/wp-content/uploads/2024/06/4-2-300x25.png 300w, https://inero-software.com/wp-content/uploads/2024/06/4-2-1030x86.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/4-2-768x64.png 768w" sizes="(max-width: 1200px) 100vw, 1200px" /></a></p>
<p></p></div><div class="col-sm-1"></div></div>
<p>Artykuł <a href="https://inero-software.com/custom-sms-authenticator-with-keycloak/">Behind the Scenes: Custom SMS Authenticator with Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com">Inero Software - Software Consulting</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5838</post-id>	</item>
		<item>
		<title>Step-by-Step Guide to Enabling Multi-Factor Authentication (MFA) in Keycloak</title>
		<link>https://inero-software.com/multi-factor-authentication-in-keycloak/</link>
		
		<dc:creator><![CDATA[Marceli Formela]]></dc:creator>
		<pubDate>Wed, 05 Jun 2024 09:51:42 +0000</pubDate>
				<category><![CDATA[Blog]]></category>
		<category><![CDATA[Company]]></category>
		<category><![CDATA[Keycloak]]></category>
		<category><![CDATA[Email-based OTP]]></category>
		<category><![CDATA[keycloak]]></category>
		<category><![CDATA[MFA]]></category>
		<category><![CDATA[Multi-Factor Authentication]]></category>
		<category><![CDATA[OTP]]></category>
		<category><![CDATA[OTP via Authenticator Apps]]></category>
		<category><![CDATA[push notifications]]></category>
		<category><![CDATA[SMS-based OTP]]></category>
		<guid isPermaLink="false">https://inero-software.com/?p=5794</guid>

					<description><![CDATA[<p>Artykuł <a href="https://inero-software.com/multi-factor-authentication-in-keycloak/">Step-by-Step Guide to Enabling Multi-Factor Authentication (MFA) in Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com">Inero Software - Software Consulting</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="row"><div class="col-sm-1"></div><div class="col-sm-10">
<p><span style="font-weight: 400;">In today&#8217;s digital age, the importance of securing online resources has never been greater. As these threats continue to evolve, the traditional method of relying on passwords has proven insufficient in protecting sensitive information. This escalation underscored the necessity for more sophisticated security measures, leading to the widespread adoption of Multi-Factor-Authentication (MFA). In this blog post, we will explore still growing online threats and how MFA serves as a defense mechanism for our applications.</span></p>
<h3><b>What is MFA?</b></h3>
<p><span style="font-weight: 400;">Multi-Factor Authentication (MFA) enhances the security of your applications by requiring users to provide multiple forms of identification before granting access. Of course, tools like Keycloak support MFA and allow administrators to configure it with ease. This guide offers a detailed, step-by-step procedure to enable MFA in Keycloak, ensuring that your user authentication processes are more secure.</span></p>
<p><span style="font-weight: 400;">MFA is designed to protect users against the vulnerabilities associated with single-factor authentication, where a user only needs to provide one form of authentication, typically a password. MFA adds layers of security by requiring users to present multiple pieces of evidence (factors) that confirm their identity.</span></p>
<p><span style="font-weight: 400;">Authentication factor user in MFA are typically categorized into three types:</span></p>
<ol>
<li><span style="font-weight: 400;">     </span><span style="font-weight: 400;">Knowledge Factors (something you know)</span></li>
</ol>
<ul>
<li style="list-style-type: none;">
<ul>
<li><span style="font-weight: 400;">         </span><span style="font-weight: 400;">Passwords</span></li>
<li><span style="font-weight: 400;">         </span><span style="font-weight: 400;">PINs</span></li>
<li><span style="font-weight: 400;">         </span><span style="font-weight: 400;">Security questions</span></li>
</ul>
</li>
</ul>
<ol start="2">
<li><span style="font-weight: 400;">     </span><span style="font-weight: 400;">Possession Factors (something you have)</span></li>
</ol>
<ul>
<li style="list-style-type: none;">
<ul>
<li><span style="font-weight: 400;">         </span><span style="font-weight: 400;">OTP (One-Time Password) generated by an authenticator app</span></li>
<li><span style="font-weight: 400;">         </span><span style="font-weight: 400;">SMS codes</span></li>
<li><span style="font-weight: 400;">         </span><span style="font-weight: 400;">Email codes</span></li>
<li><span style="font-weight: 400;">         </span><span style="font-weight: 400;">Hardware tokens</span></li>
</ul>
</li>
</ul>
<ol start="3">
<li><span style="font-weight: 400;">     </span><span style="font-weight: 400;">Inherence Factors (something you are)</span></li>
</ol>
<ul>
<li style="list-style-type: none;">
<ul>
<li><span style="font-weight: 400;">         </span><span style="font-weight: 400;">Biometric verification (facial recognition, fingerprint etc.)</span></li>
</ul>
</li>
</ul>
<p><a href="https://inero-software.com/contact-inero-software-rd-software-house/"><img loading="lazy" decoding="async" data-attachment-id="5832" data-permalink="https://inero-software.com/multi-factor-authentication-in-keycloak/2-6/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/2-2.png" data-orig-size="1200,100" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="2" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/2-2-300x25.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/2-2-1030x86.png" tabindex="0" role="button" class="alignnone wp-image-5832 size-full" src="https://inero-software.com/wp-content/uploads/2024/06/2-2.png" alt="" width="1200" height="100" srcset="https://inero-software.com/wp-content/uploads/2024/06/2-2.png 1200w, https://inero-software.com/wp-content/uploads/2024/06/2-2-300x25.png 300w, https://inero-software.com/wp-content/uploads/2024/06/2-2-1030x86.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/2-2-768x64.png 768w" sizes="(max-width: 1200px) 100vw, 1200px" /></a></p>
<p><span style="font-weight: 400;">The first step is always initial authentication, when the user enters username and password (so called knowledge factor). After the initial authentication, the user is prompted to provide a second form of authentication and this could be an OTP sent to the phone, a biometric scan, or another form of possession/inherence factor. If both factors are successfully verified, the user is granted access to the application. Now let’s take a look at a few example types of authentication and their pros/cons.</span></p>
<h4 style="padding-left: 40px;"><b>Email-based OTP</b></h4>
<p style="padding-left: 80px;"><span style="font-weight: 400;">In this method, a temporary code is sent to the user’s registered email address, which they must enter to complete the login process. Users receive the OTP directly in the email and it does not require to install or configure any additional apps. But we should remember that email accounts can be compromised, and intercepted emails could be a significant security risk.</span></p>
<h4 style="padding-left: 40px;"><b>SMS-based OTP</b></h4>
<p style="padding-left: 80px;"><span style="font-weight: 400;">Receiving an OTP via SMS is straightforward and familiar to most users, also requiring no additional app installation. It should work on any mobile phone, making it accessible to a broader range of users. But they can be also vulnerable to interception and SIM swapping attacks, making them less secure compared to other methods. SMS delivery can also be delayed or even fail due to network issues. We’ll take a closer look at its pros and cons in the next post which covers custom authenticator development.</span></p>
<h4 style="padding-left: 40px;"><b>Push notifications</b></h4>
<p style="padding-left: 80px;"><span style="font-weight: 400;">Push notifications involve sending a real-time alert to a user’s registered mobile device, asking them to approve or deny an authentication attempt. Users are instantly notified of any login attempts, allowing them to quickly respond to any unauthorized access attempts. They also do not need to enter a one-time password (OTP), which simplified the authentication process. This method of course requires an active internet connection. But remember that infected devices could potentially compromise the security of this feature and that users basically need to be educated about recognizing legitimate push notifications to avoid accidental approvals of attack attempts.</span></p>
<h4 style="padding-left: 40px;"><b>OTP via Authenticator Apps</b></h4>
<p style="padding-left: 80px;"><span style="font-weight: 400;">OTPs generated by authenticators like Google are highly secure as they are time-based and difficult to predict. They can generate OTPs without an internet connection, making them reliable even when users are offline. In this case, users need to have access to their mobile device to generate the OTP and initial setup very often requires scanning a QR code and configuring authenticator app, which might be challenging for non-technical users.</span></p>
<p><a href="https://inero-software.com/best-keycloak-practices/"><img loading="lazy" decoding="async" data-attachment-id="5833" data-permalink="https://inero-software.com/multi-factor-authentication-in-keycloak/4-4/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/4-2.png" data-orig-size="1200,100" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="4" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/4-2-300x25.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/4-2-1030x86.png" tabindex="0" role="button" class="alignnone wp-image-5833 size-full" src="https://inero-software.com/wp-content/uploads/2024/06/4-2.png" alt="" width="1200" height="100" srcset="https://inero-software.com/wp-content/uploads/2024/06/4-2.png 1200w, https://inero-software.com/wp-content/uploads/2024/06/4-2-300x25.png 300w, https://inero-software.com/wp-content/uploads/2024/06/4-2-1030x86.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/4-2-768x64.png 768w" sizes="(max-width: 1200px) 100vw, 1200px" /></a></p>
<h3><b>How to configure OTP (via mobile authenticator) in Keycloak</b></h3>
<p><span style="font-weight: 400;">Now we can go to the Keycloak console and try to set up some basic OTP in our realm. Before attempting to enable MFA in Keycloak, ensure you have a running instance of Keycloak, administrative access to the server, and a basic understanding of realm, client, user management concepts from previous posts.</span></p>
<h4><b>Step 1: OTP Policy</b></h4>
<p><span style="font-weight: 400;">From the side menu select the realm where you want to enable MFA. In the realm settings, navigate to the Authentication section and select the OTP Policy tab. Configure settings according to your security requirements. You can select default values that are provided by the server.</span></p>
<p><img loading="lazy" decoding="async" data-attachment-id="5797" data-permalink="https://inero-software.com/multi-factor-authentication-in-keycloak/2-4/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/2.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="2" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/2-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/2-1030x579.png" tabindex="0" role="button" class="wp-image-5797 aligncenter" src="https://inero-software.com/wp-content/uploads/2024/06/2-300x169.png" alt="" width="703" height="396" srcset="https://inero-software.com/wp-content/uploads/2024/06/2-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/2-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/2-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/2-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/2-533x300.png 533w, https://inero-software.com/wp-content/uploads/2024/06/2.png 1920w" sizes="(max-width: 703px) 100vw, 703px" /></p>
<h4><b>Step 2: Required actions</b></h4>
<p><span style="font-weight: 400;">In the Authentication settings, go to the Required Actions tab. Now you can activate OTP as default action for every new user.</span></p>
<p><img loading="lazy" decoding="async" data-attachment-id="5796" data-permalink="https://inero-software.com/multi-factor-authentication-in-keycloak/1-3/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/1.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="1" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/1-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/1-1030x579.png" tabindex="0" role="button" class="wp-image-5796 aligncenter" src="https://inero-software.com/wp-content/uploads/2024/06/1-300x169.png" alt="" width="701" height="395" srcset="https://inero-software.com/wp-content/uploads/2024/06/1-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/1-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/1-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/1-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/1-533x300.png 533w, https://inero-software.com/wp-content/uploads/2024/06/1.png 1920w" sizes="(max-width: 701px) 100vw, 701px" /></p>
<p><img loading="lazy" decoding="async" data-attachment-id="5798" data-permalink="https://inero-software.com/multi-factor-authentication-in-keycloak/3-2/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/3.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="3" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/3-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/3-1030x579.png" tabindex="0" role="button" class="wp-image-5798 aligncenter" src="https://inero-software.com/wp-content/uploads/2024/06/3-300x169.png" alt="" width="701" height="395" srcset="https://inero-software.com/wp-content/uploads/2024/06/3-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/3-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/3-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/3-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/3-533x300.png 533w, https://inero-software.com/wp-content/uploads/2024/06/3.png 1920w" sizes="(max-width: 701px) 100vw, 701px" /></p>
<p><span style="font-weight: 400;">Therefore, we have already configured MFA and each newly registered user will have to use it. Of course, this configuration could be modified through in-the-app account settings so that users only use MFA if they specifically request it.</span></p>
<p><img loading="lazy" decoding="async" data-attachment-id="5799" data-permalink="https://inero-software.com/multi-factor-authentication-in-keycloak/4-2/" data-orig-file="https://inero-software.com/wp-content/uploads/2024/06/4.png" data-orig-size="1920,1080" data-comments-opened="0" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="4" data-image-description="" data-image-caption="" data-medium-file="https://inero-software.com/wp-content/uploads/2024/06/4-300x169.png" data-large-file="https://inero-software.com/wp-content/uploads/2024/06/4-1030x579.png" tabindex="0" role="button" class="wp-image-5799 aligncenter" src="https://inero-software.com/wp-content/uploads/2024/06/4-300x169.png" alt="" width="701" height="395" srcset="https://inero-software.com/wp-content/uploads/2024/06/4-300x169.png 300w, https://inero-software.com/wp-content/uploads/2024/06/4-1030x579.png 1030w, https://inero-software.com/wp-content/uploads/2024/06/4-768x432.png 768w, https://inero-software.com/wp-content/uploads/2024/06/4-1536x864.png 1536w, https://inero-software.com/wp-content/uploads/2024/06/4-533x300.png 533w, https://inero-software.com/wp-content/uploads/2024/06/4.png 1920w" sizes="(max-width: 701px) 100vw, 701px" /></p>
<p>&nbsp;</p>
<p><span style="font-weight: 400;">As we can see, MFA is a powerful tool for protecting sensitive information and enhancing the security of web applications. By requiring multiple forms of verification, it makes it significantly harder for unauthorized users to access accounts and systems, mitigating risks associated with password-only authentication. Implementing this mechanism is surely helping organizations comply with regulations and protect against still-evolving web threats.</span></p>
<p><span style="font-weight: 400;">In the next article, we will take a closer look at a custom SMS authenticator for Keycloak, exploring its pros and cons.</span></p>
<p></p></div><div class="col-sm-1"></div></div>
<p>Artykuł <a href="https://inero-software.com/multi-factor-authentication-in-keycloak/">Step-by-Step Guide to Enabling Multi-Factor Authentication (MFA) in Keycloak</a> pochodzi z serwisu <a href="https://inero-software.com">Inero Software - Software Consulting</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5794</post-id>	</item>
	</channel>
</rss>
