Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
---
title: "Security shouldn't cost extra — MFA isn't an add-on"
seoTitle: "FortiToken Alternative: Why Legacy VPNs Overcharge for MFA"
publishDate: 2025-11-05
description: "MFA is mandatory under frameworks like NIS2. See why legacy VPNs sell it as an add-on, how that creates cost and risk, and how Defguard builds it in by default."
description: "Stop paying the 'security tax.' Legacy VPNs like Fortinet treat MFA as a costly add-on. See how Defguard's built-in MFA (a FortiToken alternative) meets NIS2."
author: "Robert (Co-Founder, Defguard)"
image: "/images/blog/mfa-isnt-an-addon/mfa-hero.png"
draft: true
---

![Security shouldn't cost extra — MFA isn't an add-on](/images/blog/mfa-isnt-an-addon/mfa-hero.png)
Expand Down Expand Up @@ -131,8 +133,8 @@ Yes. Defguard, as a modern WireGuard®-based platform, includes MFA by default
"@graph": [
{
"@type": "BlogPosting",
"headline": "Security shouldn't cost extra — MFA isn't an add-on",
"description": "MFA is mandatory under frameworks like NIS2. Learn why legacy VPNs sell it as an add-on and how Defguard builds it in by default.",
"headline": "FortiToken Alternative: Why Legacy VPNs Overcharge for MFA",
"description": "Stop paying the security tax. Legacy VPNs like Fortinet treat MFA as a costly add-on. See how Defguard's built-in MFA meets NIS2 compliance.",
"image": "https://defguard.net/images/blog/mfa-isnt-an-addon/mfa-hero.png",
"author": {
"@type": "Person",
Expand All @@ -154,7 +156,7 @@ Yes. Defguard, as a modern WireGuard®-based platform, includes MFA by default
"datePublished": "2025-11-05",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "https://defguard.net/blog/mfa-isnt-an-addon/"
"@id": "https://defguard.net/blog/fortitoken-alternative-vpn-mfa/"
},
"articleSection": "Security, VPN, MFA, WireGuard",
"keywords": ["MFA", "FortiToken", "WireGuard", "NIS2", "VPN", "Defguard", "SSO", "IdP"]
Expand Down Expand Up @@ -199,3 +201,4 @@ Yes. Defguard, as a modern WireGuard®-based platform, includes MFA by default
]
}`}
</script>

1 change: 1 addition & 0 deletions src/content/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { defineCollection, z } from "astro:content";
// Schema definitions
const blogSchema = z.object({
title: z.string(),
seoTitle: z.string().optional(),
publishDate: z.date(),
description: z.string(),
draft: z.boolean().optional().default(false),
Expand Down
121 changes: 120 additions & 1 deletion src/pages/blog/[slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const formatDate = (date: Date) => {
});
};

const title = `${entry.data.title} | Defguard Blog`;
const title = `${entry.data.seoTitle || entry.data.title} | Defguard Blog`;
const description = entry.data.description;
const url = `https://defguard.net/blog/${entry.slug}`;

Expand Down Expand Up @@ -162,8 +162,74 @@ const hasCaseStudy =
</main>

<MoreStories currentSlug={entry.slug} posts={allPosts} />

<!-- Image Lightbox -->
<div id="image-lightbox" class="lightbox">
<span class="lightbox-close">&times;</span>
<img class="lightbox-content" id="lightbox-img" alt="" />
<div class="lightbox-caption" id="lightbox-caption"></div>
</div>
</ProductLayout>

<script>
// Image lightbox functionality
document.addEventListener('DOMContentLoaded', () => {
const lightbox = document.getElementById('image-lightbox');
const lightboxImg = document.getElementById('lightbox-img') as HTMLImageElement;
const lightboxCaption = document.getElementById('lightbox-caption');
const closeBtn = document.querySelector('.lightbox-close');

// Get all images in post content (excluding hero images in header)
const postContent = document.querySelector('.post-content');
if (postContent) {
const images = postContent.querySelectorAll('img');

images.forEach((img) => {
// Add cursor pointer to indicate clickable
img.style.cursor = 'pointer';

img.addEventListener('click', () => {
if (lightbox && lightboxImg && lightboxCaption) {
lightbox.style.display = 'flex';
lightboxImg.src = img.src;
lightboxImg.alt = img.alt;
lightboxCaption.textContent = img.alt || '';
// Prevent body scroll when lightbox is open
document.body.style.overflow = 'hidden';
}
});
});
}

// Close lightbox
const closeLightbox = () => {
if (lightbox) {
lightbox.style.display = 'none';
document.body.style.overflow = 'auto';
}
};

if (closeBtn) {
closeBtn.addEventListener('click', closeLightbox);
}

if (lightbox) {
lightbox.addEventListener('click', (e) => {
if (e.target === lightbox) {
closeLightbox();
}
});
}

// Close on ESC key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeLightbox();
}
});
});
</script>

<style lang="scss">
// Removed global overflow overrides that were interfering with sticky positioning

Expand Down Expand Up @@ -545,4 +611,57 @@ const hasCaseStudy =
}
}
}

/* Image Lightbox Styles */
.lightbox {
display: none;
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.95);
justify-content: center;
align-items: center;
flex-direction: column;
padding: 20px;

.lightbox-content {
max-width: 95%;
max-height: 85vh;
width: auto;
height: auto;
object-fit: contain;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
border-radius: 4px;
margin: 0;
}

.lightbox-close {
position: absolute;
top: 20px;
right: 40px;
color: #fff;
font-size: 48px;
font-weight: 300;
cursor: pointer;
transition: color 0.2s ease;
line-height: 1;
user-select: none;

&:hover {
color: var(--primary-button-bg, #0c8ce0);
}
}

.lightbox-caption {
color: #fff;
text-align: center;
padding: 15px;
max-width: 800px;
font-size: 16px;
margin-top: 15px;
}
}
</style>