This guide takes you from a plain MikroTik VPN to one where every login must be approved with a tap in the Notakey Authenticator app, using nothing but RouterOS scripting. There is no RADIUS server in this setup, no auth proxy, and nothing to deploy inside your network beyond the router you already have.
All scripts come from the official
notakey/mikrotik-2fa-vpn
repository.
How it works
Understanding the flow makes every step below obvious:
- The user dials the VPN and authenticates with their username and password
as usual. The PPP session comes up, but the firewall immediately blocks
all of their traffic, because their address lands in a
vpn_pendingaddress list. - The PPP profile’s up script fires. It calls your Notakey Authentication Server, which pushes an approval request to the user’s phone: “Log in as <user> from <caller IP>?”
- If the user approves within the timeout (60 seconds), the script
removes their address from
vpn_pending: the firewall block lifts and traffic flows. - If the user denies or the request times out, the script kills the PPP session. The password alone was never enough.
Requirements
- RouterOS 6.46.4 or newer (the scripts also run on RouterOS 7.x).
- A working PPP-based VPN on the router (L2TP/IPsec, PPTP or SSTP) that users can already connect to with username and password.
- A Notakey Authentication Server reachable from the router. The free cloud tier at signup.notakey.com is enough (first 6 users free), or run the on-premise appliance (first 10 users free).
- An iOS or Android phone with the Notakey Authenticator app.
Step 1 — Prepare the Notakey side
- In your Notakey dashboard, create a new application (service) for the VPN. Name it something recognizable, e.g. “Office VPN”; this name is what users see on their phone.
- Copy the application’s Access ID (a UUID). You’ll paste it into the router script in step 4.
- Create the user (or connect your user source) and configure an onboarding requirement, then onboard the phone: install Notakey Authenticator, add your service, and complete enrollment.
The username in Notakey must exactly match the VPN username: the router
script uses the PPP username to address the push notification. john on the
router and john.doe in Notakey will silently fail.
Step 2 — Install the JParseFunctions library
RouterOS scripts can’t parse JSON natively; this library (from the mikrotik-json-parser project) fills the gap.
In WinBox or WebFig, go to System → Scripts, create a new script named
exactly JParseFunctions, leave the policy checkboxes at their defaults (no
special privileges required), and paste in the entire contents of the
JParseFunctions file
from that project.
Step 3 — Install the NotakeyFunctions library
Same procedure: create a second script named exactly NotakeyFunctions and
paste in the contents of
NotakeyFunctions.lua
from the Notakey repository. This is the client that talks to the Notakey
API, creating the authentication request and polling for the user’s answer.
The script names matter: the profile script in the next step loads both libraries by name.
Step 4 — Attach the up/down scripts to a PPP profile
Open your VPN’s PPP profile (PPP → Profiles), or create a dedicated one so 2FA applies only to selected users while you test.
- On the General tab, set Address List to
vpn_pending. Every new connection using this profile now gets its address added to that list the moment the session comes up. - On the Scripts tab, paste the On Up script below (from
PppProfileScript.lua) into the On Up box, changing two values for your environment:ntkHost, your Notakey server’s hostname (withouthttps://), andntkAccessId, the application Access ID you copied in step 1.
{
:local localAddr $"local-address";
:local remoteAddr $"remote-address";
:local callerId $"caller-id";
:local calledId $"called-id";
:log info "$user (srcIp=$callerId, dstIp=$calledId) connected: was given $remoteAddr IP (GW $localAddr)";
/system script run "JParseFunctions";
:global JSONUnload;
/system script run "NotakeyFunctions";
:global NtkAuthRequest;
:global NtkWaitFor;
:global NtkUnload;
# Change values below to match your Notakey installation
:local ntkHost "demo.notakey.com";
:local ntkAccessId "12345645-b32b-4788-a00d-251cd7dc9a03";
# Custom message in authentication request
:local authDescMsg "Log in as $user from $callerId\?";
:local ntkAuthUuid ([$NtkAuthRequest host=$ntkHost accessId=$ntkAccessId authUser=$user authTitle="VPN connection" authDesc=$authDescMsg authTtl=60]);
:if ([$NtkWaitFor uuid=$ntkAuthUuid host=$ntkHost accessId=$ntkAccessId]) do={
# Remove blocking rule after successful 2FA authentication
/ip firewall address-list remove [/ip firewall address-list find where list=vpn_pending address=$remoteAddr]
} else={
:log info "VPN 2FA authentication failure for user $user from IP $callerId";
# Disconnect active session with this IP and user
/ppp active remove [/ppp active find name="$user" address="$remoteAddr"]
}
$NtkUnload
$JSONUnload
}
The On Down script from the same file just logs the disconnect. Paste it into the On Down box for tidy logs, or skip it.
The approval timeout (authTtl) is 60 seconds: long enough to pull a
phone out of a pocket, short enough that a stalled login doesn’t hold a
half-open session forever.
Step 5 — Add the firewall rules
The drop rule is what makes the pending list actually mean something:
/ip firewall filter
add chain=forward src-address-list=vpn_pending action=drop \
comment="Block VPN clients pending 2FA approval"
Place it above your general VPN-allow rules so it wins while the approval is
pending. If VPN clients should also be blocked from reaching the router
itself, mirror the rule in the input chain.
One special case: if the approval will happen on the same device that’s dialing the VPN (the phone itself is the VPN client), the approval traffic must be able to reach the Notakey server while everything else is still blocked. Add an accept rule above the drop:
/ip firewall filter
add chain=forward src-address-list=vpn_pending dst-address=<notakey-server-ip> \
action=accept comment="Let pending clients reach Notakey for approval"
(Approving from a second device, where the phone approves while a laptop
dials, needs no extra rule: the router polls the Notakey API itself, and its
own traffic isn’t subject to the forward chain.)
Step 6 — Assign the profile and test
Point your VPN user (PPP → Secrets) at the profile from step 4, then dial the VPN from a client:
- The tunnel connects, but nothing routes: you’re in
vpn_pending. - Within a second or two, the push notification lands on the phone.
- Approve → traffic starts flowing. Deny or ignore → after the timeout the session drops.
Watch it happen in Log (the scripts log each request and outcome) and in
IP → Firewall → Address Lists, where the client’s address appears in
vpn_pending and vanishes on approval.
Troubleshooting
- No push arrives. Almost always a username mismatch between PPP Secrets and the Notakey service, or the phone was never onboarded to this particular application.
- Push arrives but you can’t approve it. You’re probably approving from the same device that’s dialing the VPN, and the pending-list drop rule is blocking the approval traffic. Add the accept rule from step 5, or approve from a device that isn’t inside the tunnel.
- Request text looks garbled or the request fails. The JSON parser library can’t handle parentheses (and possibly other special punctuation) in the authentication message. Keep the message text plain.
- Works for one user, not another. Confirm the second user’s profile assignment; only secrets pointing at the 2FA profile get the treatment.
Where this fits
This script-based approach is the fastest route to MikroTik VPN 2FA and needs no extra infrastructure. If you later outgrow it (many routers, other VPN vendors, or central policy), the same Notakey server supports a RADIUS-based setup where an auth proxy handles the approval flow instead of router scripts.