0 to Infinity Lesson

Android PIN Login with WebAuthn and Passkeys

Learn how a website can let users sign in with Android screen lock, PIN, pattern, password, fingerprint, or face unlock without ever seeing the actual Android PIN.

L
Android Screen Lock

The browser asks Android to verify the user. The website never gets the PIN.

1
2
3
4
5
6
7
8
9

Level 0: What does “login using Android PIN” mean?

It does not mean that the web page asks the user to type the Android PIN into an HTML input box. That would be unsafe and Android does not allow it.

A website cannot read the Android PIN, pattern, password, fingerprint, or face data.

The correct meaning is this:

The website asks the browser to authenticate the user. The browser asks Android. Android verifies the user with screen lock. The website receives a secure result.

Simple classroom example

Imagine a school gate. The class teacher does not ask for your house key. The gatekeeper checks your identity and tells the teacher, “Yes, this is the right student.” Similarly, the website does not get your Android PIN. Android checks it and tells the browser whether the authentication succeeded.

Level 1: Why plain HTML cannot do this

HTML can create forms, buttons, inputs, and layouts. JavaScript can respond to clicks and run browser APIs. But the Android PIN belongs to the operating system security layer. A normal website must not be allowed to read it.

Wrong approach

Create an input field and ask the user to enter the Android PIN.

This is not real Android PIN login. It is only a normal password field.

Correct approach

Use WebAuthn and passkeys.

Android verifies the user locally and the website receives cryptographic proof.

Bad example

<input type="password" placeholder="Enter Android PIN">

This is not Android PIN login. This only creates a password box. The browser has no permission to compare this value with the real Android screen lock.

Level 2: WebAuthn, passkeys, and platform authenticators

WebAuthn means Web Authentication. It allows websites to use public-key credentials for secure login. In normal passwords, the same secret is typed by the user and stored or verified by the server. In WebAuthn, the private key stays with the authenticator, and the server stores a public key.

Term Meaning
WebAuthn Web Authentication API used for passwordless and strong login.
Passkey A user-friendly credential that can be unlocked using screen lock or biometrics.
Platform authenticator The built-in authenticator on the device, such as Android device security.
Relying Party The website or app that wants to authenticate the user.
Challenge A random value generated during registration or login to prevent replay attacks.
Public key Stored by the server and used to verify signatures.
Private key Kept safely by the authenticator. The website never receives it.

The registration flow

  1. User clicks “Register Android PIN Login”.
  2. Website creates a random challenge.
  3. Browser calls navigator.credentials.create().
  4. Android asks for PIN, pattern, password, fingerprint, or face unlock.
  5. Authenticator creates a key pair.
  6. Website stores the credential ID and public key.

The login flow

  1. User clicks “Login Using Android PIN”.
  2. Website creates a fresh challenge.
  3. Browser calls navigator.credentials.get().
  4. Android verifies the user locally.
  5. Authenticator signs the challenge using the private key.
  6. Server verifies the signature using the stored public key.

Level 3: Working browser-side demo

This is a learning demo. It stores only a simplified credential reference in localStorage. A production website must use a server to generate challenges and verify responses.

Run this page on HTTPS or localhost. WebAuthn usually does not work from a plain file opened as file://.

Ready.

Level 3A: JavaScript used in the demo

The important browser objects are PublicKeyCredential, navigator.credentials.create(), and navigator.credentials.get().

const statusBox = document.getElementById("status");
const usernameInput = document.getElementById("username");

function setStatus(message, type = "") {
  statusBox.innerHTML = type
    ? `<span class="${type}">${message}</span>`
    : message;
}

function bufferToBase64Url(buffer) {
  const bytes = new Uint8Array(buffer);
  let binary = "";

  for (const byte of bytes) {
    binary += String.fromCharCode(byte);
  }

  return btoa(binary)
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=/g, "");
}

function base64UrlToBuffer(base64url) {
  const base64 = base64url
    .replace(/-/g, "+")
    .replace(/_/g, "/");

  const padded = base64.padEnd(
    base64.length + (4 - base64.length % 4) % 4,
    "="
  );

  const binary = atob(padded);
  const bytes = new Uint8Array(binary.length);

  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }

  return bytes.buffer;
}

function randomChallenge(length = 32) {
  const array = new Uint8Array(length);
  crypto.getRandomValues(array);
  return array;
}

async function checkSupport() {
  if (!window.PublicKeyCredential) {
    throw new Error("WebAuthn is not supported in this browser.");
  }

  const available =
    await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();

  if (!available) {
    throw new Error(
      "No user-verifying platform authenticator was found. Set a PIN, pattern, password, or biometric lock first."
    );
  }
}

async function registerPasskey() {
  try {
    await checkSupport();

    const username = usernameInput.value.trim();

    if (!username) {
      setStatus("Please enter a username.", "bad");
      return;
    }

    const userId = new TextEncoder().encode(username);

    const publicKeyOptions = {
      challenge: randomChallenge(),

      rp: {
        name: "Learn With Champak Demo"
      },

      user: {
        id: userId,
        name: username,
        displayName: username
      },

      pubKeyCredParams: [
        { type: "public-key", alg: -7 },
        { type: "public-key", alg: -257 }
      ],

      authenticatorSelection: {
        authenticatorAttachment: "platform",
        userVerification: "required",
        residentKey: "preferred"
      },

      timeout: 60000,
      attestation: "none"
    };

    setStatus("Android will now ask for screen lock confirmation...");

    const credential = await navigator.credentials.create({
      publicKey: publicKeyOptions
    });

    const savedCredential = {
      id: credential.id,
      rawId: bufferToBase64Url(credential.rawId),
      type: credential.type,
      username
    };

    localStorage.setItem("androidPinCredential", JSON.stringify(savedCredential));

    setStatus("Android PIN login registered successfully.", "ok");
  } catch (error) {
    setStatus(error.message || "Registration failed.", "bad");
  }
}

async function loginWithPasskey() {
  try {
    await checkSupport();

    const saved = localStorage.getItem("androidPinCredential");

    if (!saved) {
      setStatus("No saved login found. Register first.", "bad");
      return;
    }

    const credentialData = JSON.parse(saved);

    const publicKeyOptions = {
      challenge: randomChallenge(),

      allowCredentials: [
        {
          id: base64UrlToBuffer(credentialData.rawId),
          type: "public-key",
          transports: ["internal"]
        }
      ],

      userVerification: "required",
      timeout: 60000
    };

    setStatus("Android will now ask for PIN, pattern, password, fingerprint, or face unlock...");

    const assertion = await navigator.credentials.get({
      publicKey: publicKeyOptions
    });

    if (assertion) {
      setStatus(`Login successful. Welcome, ${credentialData.username}.`, "ok");
    } else {
      setStatus("Login failed.", "bad");
    }
  } catch (error) {
    setStatus(error.message || "Login failed.", "bad");
  }
}

Level 4: What changes in a real website?

The browser demo is useful for learning, but real authentication needs a backend. The server must create the challenge, store the public key, and verify the signed response.

Demo page Real website
Challenge generated in browser Challenge generated on server
Credential reference saved in localStorage Credential ID and public key saved in database
Login accepted after browser returns assertion Server verifies signature, origin, challenge, counter, and user
Good for classroom demo Required for production

Server-side registration endpoints

POST /webauthn/register/options
Server returns:
{
  "challenge": "...",
  "rp": "...",
  "user": "...",
  "pubKeyCredParams": [...]
}

POST /webauthn/register/verify
Browser sends credential response.
Server verifies it and stores the public key.

Server-side login endpoints

POST /webauthn/login/options
Server returns a fresh challenge.

POST /webauthn/login/verify
Browser sends assertion.
Server verifies the signature using the stored public key.

Level 5: Production checklist

Use HTTPS

Passkeys and WebAuthn require secure browser conditions.

Verify on server

Never trust only browser-side success for real login.

Use fresh challenges

Every registration and login attempt should use a new random challenge.

Check origin

Verify that the response came from your real domain.

Offer recovery

Users may lose a phone, reset Android, or change devices.

Keep fallback login

During transition, keep email login, password login, or one-time code login.

Common mistakes

Level 6: How to explain this to students

Use this simple sentence:

Android PIN login on the web means Android confirms the user, not that the website reads the PIN.

Analogy

Your phone is like a locked cupboard. The website asks, “Can the real owner unlock this cupboard?” Android checks the owner locally. If the owner succeeds, Android gives the website a signed proof. The website never sees the cupboard key.

Practice Quiz

1. Can HTML directly read the Android PIN?
No. Android does not expose the real screen lock PIN to websites.
2. Which API is used for passkey login?
WebAuthn, using the browser Credential Management API.
3. What stays on the device: public key or private key?
The private key stays protected by the device or authenticator.
4. What should the server store?
The credential ID and public key, not the Android PIN.
5. Why is localStorage not enough for real login?
Because real authentication must be verified by the server, not trusted only in the browser.

Mini Project

Build a small login page with three buttons:

  1. Check WebAuthn support.
  2. Register passkey.
  3. Login with passkey.

Then improve it by adding a Node.js backend with four routes:

POST /register/options
POST /register/verify
POST /login/options
POST /login/verify

The browser should only start the authentication process. The backend should make the final login decision.