Skip to content

[idea] SourceAFIS as Keycloak plugin #73

@delcroip

Description

@delcroip

Hi,

In my work (NGO) I got the feeling that there is few or none opensource fingerprint management platform

while looking for one I got the idea that SourceAFIS might be a solution if turned into a keycloak plugin, not knowing that field it might be a silly idea, so here I am asking you if there is obvious reason not to do it.

Is there any reason not to do it ? If not I will try to have a closer llok if time premits.

here LLM code proposed after my prompt:

"""
would it be possible to create a keyclaok plugin that implement SourceAFIS and save the template in such way that it would be linked to the user ?
"""

import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import com.machinezoo.sourceafis.FingerprintTemplate;
import com.machinezoo.sourceafis.FingerprintMatcher;
import javax.ws.rs.core.Response;
import java.util.Base64;

public class FingerprintAuthenticator implements Authenticator {
    private static final String TEMPLATE_ATTRIBUTE = "fingerprint_template";

    @Override
    public void authenticate(AuthenticatorContext context) {
        KeycloakSession session = context.getSession();
        UserModel user = context.getUser();
        RealmModel realm = context.getRealm();

        // Get fingerprint data from client (e.g., via form or API)
        String fingerprintData = context.getHttpRequest().getFormParameters().getFirst("fingerprint_data");
        if (fingerprintData == null) {
            context.failureChallenge(Response.status(400).entity("Fingerprint data missing").build());
            return;
        }

        try {
            // Convert base64 fingerprint data to byte array
            byte[] probeImage = Base64.getDecoder().decode(fingerprintData);
            FingerprintTemplate probe = new FingerprintTemplate(probeImage);

            // Retrieve stored template from user attributes
            String storedTemplateBase64 = user.getFirstAttribute(TEMPLATE_ATTRIBUTE);
            if (storedTemplateBase64 == null) {
                context.failureChallenge(Response.status(400).entity("No fingerprint template enrolled").build());
                return;
            }

            byte[] storedTemplateBytes = Base64.getDecoder().decode(storedTemplateBase64);
            FingerprintTemplate candidate = new FingerprintTemplate(storedTemplateBytes);

            // Match fingerprints using SourceAFIS
            FingerprintMatcher matcher = new FingerprintMatcher(probe);
            double score = matcher.match(candidate);

            // Threshold for successful match (adjust as needed)
            double threshold = 40.0; // SourceAFIS recommends ~40 for 1:1 verification
            if (score >= threshold) {
                context.success();
            } else {
                context.failureChallenge(Response.status(401).entity("Fingerprint verification failed").build());
            }
        } catch (Exception e) {
            context.failureChallenge(Response.status(500).entity("Error processing fingerprint: " + e.getMessage()).build());
        }
    }

    @Override
    public void action(AuthenticatorContext context) {
        // Handle enrollment action
        String fingerprintData = context.getHttpRequest().getFormParameters().getFirst("fingerprint_data");
        if (fingerprintData != null) {
            try {
                // Validate and store fingerprint template
                byte[] templateBytes = Base64.getDecoder().decode(fingerprintData);
                FingerprintTemplate template = new FingerprintTemplate(templateBytes);
                String templateBase64 = Base64.getEncoder().encodeToString(templateBytes);

                // Store template in user attributes (encrypt in production)
                UserModel user = context.getUser();
                user.setSingleAttribute(TEMPLATE_ATTRIBUTE, templateBase64);
                context.success();
            } catch (Exception e) {
                context.failureChallenge(Response.status(500).entity("Error enrolling fingerprint: " + e.getMessage()).build());
            }
        }
    }

    @Override
    public boolean requiresUser() {
        return true;
    }

    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
        return user.getFirstAttribute(TEMPLATE_ATTRIBUTE) != null;
    }

    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
        // Require fingerprint enrollment if not configured
        user.addRequiredAction("ENROLL_FINGERPRINT");
    }

    @Override
    public void close() {
        // No resources to close
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions