Unlocking a Website with an NTAG

I’ve been working on a website project adapted for security, and I now use my XSIID blinking implant to unlock a website I built in place (and in addition) to a passcode and other tokens. I wanted to share the code I use here, so other people can see how to build a basic security firewall with an implant.

I’m mostly using JavaScript, reading and writing randomly generated data to the tag, and then posting the data written and read for confirmation with the server. If the data is valid, I am able to log in. And it’s as easy as holding my phone to my hand. I also have a few NFC rings I use for this, as well as regular credit card sized NTAGs (NTAG216).

I also use Python as a backend with Django. This template extends a base template with BootStrap and JQuery, and takes a single optional URL parameter, “generate”, used to tell the software to write to the tag even when a read is invalid. This is in order to train the tags, and also because very rarely a tag can be corrupted when there is a successful write but the server is cut off from caching the new data written to it.

The software identifies the tag by its ID (MAC address) and writes bytes to the tag.

{% extends 'base.html' %}
{% block content %}
<legend>NFC Verification</legend>
<p>Place the NFC device near the back of the phone. <a href="{% url 'security:mrz' %}" title="Use MRZ instead">Use MRZ instead</a></p>
<form action="{{ request.path }}{% if request.GET.generate %}?generate=t{% endif %}" id="nfc-form" method="POST" class="hide">
{{ form }}
</form>
<p class="text-center" style="font-size: 40px"><i class="bi bi-reception-4"></i></p>
<p id="errors">Tap the screen to enable NFC.</p>
{% endblock %}
{% block javascript %}
var form = document.getElementById('nfc-form');
function readTag() {
  var successfulWrite = false;
  var successfulRead = false;
  if ("NDEFReader" in window) {
    const ndef = new NDEFReader();
    document.getElementById('errors').innerHtml = 'Initialized.';
    try {
     ndef.scan().then(() => {
      document.getElementById('errors').innerHTML = 'Scanning.';
      ndef.onreading = (event) => {
        if(successfulWrite) { return; }
        if(successfulRead) { return; }
        document.getElementById('errors').innerHTML= 'Read tag.';
        const decoder = new TextDecoder();
        for (const record of event.message.records) {
           document.getElementById('errors').innerHTML= 'Record detected.';
           document.getElementById('id_nfc_data_read').value = decoder.decode(record.data);
           document.getElementById('id_nfc_id').value = event.serialNumber;
           successfulRead = true;
         }
                       ndef.write("{{ nfc_data }}").then(function() {
                           successfulWrite = true;
                           document.getElementById('errors').innerHTML= 'Data written, confirming.';
                           document.getElementById('id_nfc_data_written').value = "{{ nfc_data }}";
                           var formData = new FormData(form);
            $.ajax({
               url: '{{ request.path }}{% if request.GET.generate %}?generate=t{% endif %}',
               data: formData,
               method: 'POST',
               timeout: 60 * 1000,
               cache: false,
               contentType: false,
               processData: false,
               error: function(xhr, textStatus, errorThrown) {
                       successfulRead = false;
                       successfulWrite = false;
                   document.getElementById('errors').innerHTML = errorThrown.message;
               },
               success: function(response) {
                   if(response == 'y') {
                   document.getElementById('errors').innerHTML= 'Redirecting.';
                           window.navigator.vibrate({{ default_vibration }});
                           window.location.href = '{% if request.GET.next %}{{ request.GET.next }}{% else %}/go/{% endif %}';
                   } else {
                       successfulRead = false;
                       successfulWrite = false;
                       document.getElementById('errors').innerHTML = 'Invalid tag.';
                   }
               }
            });
          });
       };
      });
    } catch(error) {
      document.getElementById('errors').innerHTML= error.message;
    }
  }
}
var reading = false;
function initialize() {
    if(!reading) {
        window.navigator.vibrate({{ default_vibration }});
        reading = true;
        readTag();
    }
}
document.body.addEventListener('click', function(event) {
    initialize();
});
document.body.addEventListener('touchmove', function(event) {
    initialize();
});
{% if not request.GET.generate %}
setInterval(function() {
	$.ajax({
		url: "{% url 'security:modal' %}",
		method: 'POST',
		success: function(data){
			if(data == 'y') {
	                    window.location.href = '{% if request.GET.next %}{{ request.GET.next }}{% else %}/go/{% endif %}';
			}
		}
	});
}, 15 * 1000);
{% endif %}
{% endblock %}

This also requires a view which returns a HTTP response  in case the user is already authenticated (so the passthrough happens on multiple pages at the same time when the user has already scanned a tag).

The Python is fairly simple too, with a basic model storing text:


class NFCScan(models.Model):
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='nfc_scans')
    timestamp = models.DateTimeField(default=timezone.now)
    session_key = models.CharField(max_length=100, default='', blank=True, null=True)
    nfc_id = models.TextField(blank=True, default='')
    nfc_data_read = models.TextField(blank=True, default='')
    nfc_data_written = models.TextField(blank=True, default='')
    nfc_id = models.TextField(blank=True, default='')
    nfc_name = models.TextField(blank=True, default='')
    valid = models.BooleanField(default=True)

And a view to scan the tag:


@login_required
def scan_nfc(request):
    if request.method == 'POST':
        form = NFCScanForm(request.POST)
        if form.is_valid():
            allowed = NFCScan.objects.filter(user=request.user).count() == 0 or (NFCScan.objects.filter(user=request.user, nfc_id=form.cleaned_data.get('nfc_id', None), valid=True, nfc_data_written=form.cleaned_data.get('nfc_data_read', None)).count() == 1) or (request.GET.get('generate', False) and face_mrz_or_nfc_verified(request))
            if not allowed: return HttpResponse('n')
            form.instance.user = request.user
            form.instance.session_key = request.session.session_key
            form.instance.nfc_data_written = form.cleaned_data.get('nfc_data_written', None)
            scan = form.save()
            return HttpResponse('y')
    return render(request, 'security/nfc.html', {
        'title': 'Scan NFC',
        'form': NFCScanForm(),
        'nfc_data': get_random_string(length=settings.VERIFICATION_NFC_LENGTH),
        'session_key': request.session.session_key
    })

This code is also sensitive to the session key, so I use the following test to then require an NFC scan for pages that I want to secure with the tag (for example a private blog).

def nfc_verified(request):
    user = request.user
    return user.is_authenticated and ((user.nfc_scans.filter(valid=True, session_key=request.session.session_key).last() and user.nfc_scans.filter(valid=True, session_key=request.session.session_key).last().timestamp > timezone.now() - datetime.timedelta(minutes=settings.NFC_SCAN_REQUIRED_MINUTES)) or user.user_sessions.filter(bypass=True, session_key=request.session.session_key, timestamp__gte=timezone.now() - datetime.timedelta(minutes=settings.LOGIN_VALID_MINUTES)).first())

Here’s the full code on GitHub:

This works pretty well. I have some barcode labels printed on some of my larger tags as a fallback too, in case a tag fails I can scan the barcode and parse the text on it as an additional option to verify my identity. Maybe there’s another sort of biohacking idea here? Tattoos of barcodes could be used in a similar way to log in to a site (like a QR or PDF code), though I’m not sure about fading. More reliably, I’ve been able to use the Kabsch-Umeyama algorithm to verify my identity by taking a picture of the constellations (albino speckled dark colored skin) on arms and hands.

I hope this information is useful to some of you, I’m not sure anyone has posted much about using these tags for authentication here but I expect it has real potential as a market for the tags. NFC authentication is super reliable and useful, at least in my use case, because I always have it on me and it’s compatible with any smartphone. I look forward to what the world of biohacking brings, I’m going to continue with my implant experiments with a magnet implant and potentially others. I used to have a tritium implant, but I had it removed because I did a sloppy job on the surgery and it was causing some discomfort. This implant has been really comfortable though, it’s useful and practical for my application. If you want to read more about my projects, check out my Amazon Author profile where I’m working on a few series about full stack solutions I build for all sorts of cool stuff including security, AI, ML, blogging, media, facial recognition, embedded devices, responsive websites, and more. Thanks for reading!

The books: https://www.amazon.com/stores/Daisy-Holton/author/B0CBWVPZ5C?ref=ap_rdr&store_ref=ap_rdr&isDramIntegrated=true&shoppingPortalEnabled=true

4 Likes


I’m sure there’s a better way to show your code. Try a screenshot with syntax coloration maybe and a GitHub link

Also thanks for sharing :wink:

That’s pretty cool!

2 Likes

Nice! Thanks for sharing!

Have you considered using the cmac signed spark2 URL and validation API (vivokey.com/api) for increased security?

1 Like

Thanks for the tip! I just added a GitHub gist link instead. Also, yeah, kabsch is really awesome code. It’s interesting being able to identify images of myself using just the patterns in my skin, there’s definitely something useful about it. It’s even cooler that my hand lights up now though!

1 Like

That looks really cool! I’ll think about it when it’s back in stock. For now I’m mainly working with NTAGs, because they are readily available and cheap. I’ll definitely have to look into other options too though.

Order $1 worth of custom services and I will ship you a spark2 you can play with for development. Post your stuff on GitHub. Fair deal?

4 Likes

Could also use a

Code Block

Just use three backticks around the code:
```

[Code Here]
```

You can even specify a language for syntax highlighting:

console.log("Hello World!");

```javascript
[JS Code Here]
```

Edit:
As for making it not so long and obnoxious, just put it in a

Summary

This text will be hidden

[details=“Summary”]
This text will be hidden
[/details]

5 Likes

That sounds like a great deal! I’ll make it happen. You mean this product, right? Custom Work - RFID & NFC Chip Implants and Biohacking products

just to speed the process up

Let me answer on Amals behalf,

Yes

2 Likes

Perfect! I just purchased it. I’m looking forward to trying it out! It’ll be good to get the spark2 implanted and start working with authentication.

4 Likes

Thank you for helping make the spark 2 more useful!!! It’s been a glorified business card for me for far too long lol

2 Likes

I got my spark 2 last week in the mail, I haven’t implanted it yet but I will soon and I’ll keep you guys posted. Thanks again!

I wanted to add, there’s a way to also write a URL to the NTAG using JavaScript while still using it for authentication, I use this code:

var toWrite = {records: [{
  recordType: "text",
    data: "{{ nfc_data }}"
   }, {
    recordType: "url",
    data: "{{ base_url }}"
  }],
};
ndef.write(toWrite).then(function() {

This way it’s also sort of a digital business card, other people can use it to access the website, just not to authenticate.

2 Likes

Just FYI, the Spark 2 user memory space is locked. You cannot update or write to it. This is unfortunately how key storage works on the chip. We are rebuilding a redirection service so you can effectively direct anyone who scans it to any URL you want, but for now the basic NFC sharing capability isn’t all that useful.

1 Like

Yeah, thanks for the reminder, I was aware of this. The URL writing will just be for my XSIID for now.

1 Like