Skip to content

Authentication

Self provides a secure authentication workflow that allows users to authenticate themselves using their biometrics. This workflow can be used in a variety of scenarios, such as:

  • Secure login for web applications
  • Secure login for mobile applications
  • Secure login for desktop applications

Try Authentication

Experience authentication in action with our demo applications. Each app demonstrates real-world authentication workflows with Self SDK.

Platform Quick Install Source Code
Mobile - Android Download APK View Source
Mobile - iOS Download from App Store View Source
Server - Golang docker run -it ghcr.io/joinself/self-sdk-demo:go View Source
Server - Java docker run -it ghcr.io/joinself/self-sdk-demo:java View Source

Quick Start

  1. Start a server - Use the Docker links above to run a Golang or Java server
  2. Install mobile app - Download the Android or iOS app using the links above
  3. Register Self account - Open the mobile app and follow the registration prompts
  4. Connect to server - Enter the server identifier into the mobile app to establish connection
  5. Test authentication - Follow the prompts in the mobile app to authenticate to the server

How to build an authentication workflow

You can build a simple secure authentication workflow with Self using just a few key features. Let's break down the process into steps:

1. Server: Credential Presentation Request

The server builds a new request asking the user to provide their liveness credential, which is verifiable evidence that they are a real person, and also the same real person who registered the account. The server then sends this request to the user.

content, err = message.NewCredentialPresentationRequest().
    Type([]string{"VerifiablePresentation", "CustomPresentation"}).
    Details(credential.CredentialTypeLiveness, []*message.CredentialPresentationDetailParameter{message.NewCredentialPresentationDetailParameter(message.OperatorNotEquals, "sourceImageHash", "")}).
    Finish()

if err != nil {
    log.Fatal("failed to encode credential request message", "error", err)
}

err = selfAccount.MessageSend(userAddress, content)
if err != nil {
    log.Fatal("failed to send credential request message", "error", err)
}
1
2
3
4
5
6
7
val content = NewCredentialPresentationRequest()
.presentationType(arrayOf("VerifiablePresentation", "CustomPresentation"))
.details(credentialType = arrayOf("VerifiableCredential","LivenessCredential"), parameters = arrayOf(CredentialPresentationDetailParameter.create(operator = ComparisonOperator.NOT_EQUALS, claimField = "sourceImageHash", claimValue = "")))
.finish()

val sendStatus = account.messageSend(userAddress, content)
println("send CredentialPresentation request status: ${sendStatus.code()} - requestId:${credentialRequest.id().toHexString()}")

2. Client: Handle Credential Request

Listen for liveness requests. On receipt start the Liveness UI flow. Upon complettion of the Liveness UI flow send the response back to the requesting server.

var credentialRequest: CredentialRequest? = null 
account.setOnRequestListener { msg ->
    when (msg) {
        is CredentialRequest -> {
            if (msg.details().any { it.types().contains(CredentialType.Liveness) && it.subject() == Constants.SUBJECT_SOURCE_IMAGE_HASH }) {                    
                credentialRequest = msg

                navController.navigate("livenessRoute") // launch liveness UI flow
            }
        }
    }
}

// integrate Liveness UI flow into Navigation
addLivenessCheckRoute(navController, route = "livenessRoute", selfModifier = selfModifier, account = { account }, withCredential = true,
    onFinish = { selfie, credentials ->
        if (credentialRequest != null) {
            // after receiving the liveness credentials, send the response
            sendCredentialResponse(credentials)               
        }            
    }
)

fun sendCredentialResponse(credentials: List<Credential>) {       
    val credentialResponse = CredentialResponse.Builder()
        .setRequestId(credentialRequest.id())
        .setTypes(credentialRequest.types())
        .setToIdentifier(credentialRequest.toIdentifier())
        .setFromIdentifier(credentialRequest.fromIdentifier())
        .setStatus(ResponseStatus.accepted)
        .setCredentials(credentials)
        .build()

    account.send(credentialResponse)       
}
// 1. Handle credential request in your request listener
account.setOnRequestListener { [weak self] requestMessage in
    guard let self = self else { return }

    if let credentialRequest = requestMessage as? CredentialRequest {
        Task { @MainActor in
            await self.handleCredentialRequest(credentialRequest)
        }
    }
}

// 2. Handle credential request with proper filtering and thread safety
private func handleCredentialRequest(_ credentialRequest: CredentialRequest) async {
    let allClaims: [Claim] = credentialRequest.details()
    let livenessCredentials = allClaims.filter {
        $0.types().contains(CredentialType.Liveness) &&
        $0.types().contains(CredentialType.Verifiable)
    }
    let otherCredentials = allClaims.filter {
        !($0.types().contains(CredentialType.Liveness) &&
          $0.types().contains(CredentialType.Verifiable))
    }

    // Log other credential types for debugging
    for claim in otherCredentials {
        print("Ignoring unsupported credential types: \(claim.types())")
    }

    if !livenessCredentials.isEmpty {
        await handleLivenessAuthentication(credentialRequest, livenessCredentials: livenessCredentials)
    } else {
        print("No liveness credentials found")
    }
}

// 3. Handle liveness authentication with proper thread management
private func handleLivenessAuthentication(
    _ credentialRequest: CredentialRequest,
    livenessCredentials: [Claim]
) async {
    // Analyze liveness requirements from filtered credentials
    for claim in livenessCredentials {
        let claimTypes = claim.types()
        let subject = claim.subject()
        let comparisonOperator = claim.comparisonOperator()
        print("Processing liveness claim: types=\(claimTypes), subject=\(subject), operator=\(comparisonOperator)")
    }

    // Present liveness check flow on main thread (critical for UI)
    SelfSDK.showLiveness(
        account: account,
        showIntroduction: false,
        autoDismiss: true
    ) { [weak self] selfieImageData, credentials, error in
        guard let self = self else { return }

        if let error = error {
            print("Liveness check failed: \(error)")
            return
        }

        guard !credentials.isEmpty else {
            print("No credentials provided from liveness check")
            return
        }

        // Send credential response
        Task { @MainActor in
            await self.sendCredentialResponse(
                to: credentialRequest,
                credentials: credentials
            )
        }
    }
}

// Response to the above credential request
// 4. Send credential response with proper type conversion
private func sendCredentialResponse(
    to request: CredentialRequest,
    credentials: [Any]
) async {
    // Convert credentials to proper type (critical for Swift type safety)
    let selfCredentials = credentials.compactMap { $0 as? Credential }

    guard !selfCredentials.isEmpty else {
        print("No valid credentials to send")
        return
    }

    do {
        let response = CredentialResponse.Builder()
            .withRequestId(request.id())
            .withTypes(request.types())
            .toIdentifier(request.toIdentifier())
            .withStatus(ResponseStatus.accepted)
            .withCredentials(selfCredentials)
            .build()

        try await account.send(message: response) { messageId, error in
            if let error = error {
                print("Response failed: \(error)")
            } else {
                print("Credentials sent successfully")
            }
        }
    } catch {
        print("Error building credential response: \(error)")
    }
}

3. Server: Validating Credentials

After receiving the credential presentation response, we need to validate the credentials. This involves checking the presentation's validity, the credential's validity, and extracting the claims.

for _, credential := range presentation.Credentials() {
    if credential.CredentialType()[0] == "VerifiableCredential" && credential.CredentialType()[1] == "LivenessCredential" {
        err = credential.Validate()
        if err != nil {
            log.Printf("WARN: failed to validate credential - error: %v", err)
            continue
        }

        if credential.ValidFrom().After(time.Now()) {
            log.Println("WARN: credential is intended to be used in the future")
            continue
        }

        claims, err := credential.CredentialSubjectClaims()
        if err != nil {
            log.Printf("WARN: failed to parse credential claims - error: %v", err)
            continue
        }

        for k, v := range claims {
            log.Printf("INFO: credential value - credentialType: %s, field: %s, value: %v", credential.CredentialType(), k, v)
        }
    }
}
presentation.credentials().forEach { credential ->
    credential.validate()

    val claims = credential.credentialSubjectClaims()

    claims.forEach {
        println(
            "credential value" +
            "\ncredentialType:${credential.credentialType()}" +
            "\nfield:${it.key}" +
            "\nvalue:${it.value}"
        )
    }
}

Conclusion

Integrating authentication with the Self SDK enables you to provide secure, passwordless, and privacy-preserving login experiences for your users. By leveraging liveness detection and verifiable credentials, you ensure that only real, verified users can access your application—without the risks and friction of traditional authentication methods.

With pre-built UI components and cross-platform support, adding advanced authentication to your app is straightforward and efficient.
Explore the demo apps and source code to see Self authentication in action, and start building secure, user-friendly authentication flows in your own projects today.

For further details and advanced use cases, refer to the Essentials section or browse our examples repository for working implementations.