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 from Play Store 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, display the request UI, and accept the request. Once accepted, the Liveness flow will begin and send the response back to the requesting server.

// 1. When setting up an account, listen to the request in callbacks.
override fun onMessage(message: Message) {            
    when (message) {
        is CredentialRequest -> {
            Log.d(LOGTAG, "received credential message")
            request = message
        }                
    }
}

// 2. Show the UI for the request and let users accept or reject it. The UI is rendered by the Self SDK.
account.DisplayRequestUI(selfModifier, request as CredentialRequest,
    onFinish = { isSuccess, status ->
        request = null
    }
)

You can find the full example at https://github.com/joinself/self-sdk-examples/blob/main/android/credential/src/main/java/com/joinself/example/MainActivity.kt

// 1. When setting up an account, handle delegation to listen to the request in callbacks.
let account = Account.Builder()
        .withEnvironment(Environment.production)
        .withSandbox(true) // if true -> production
        .withGroupId("") // ex: com.example.app.your_app_group
        .withDelegate(delegate: YourClass_instance)
        .withStoragePath(FileManager.storagePath)
        .build()

class YourClass: AccountDelegate {
    func onConnect() {
    print("onConnect")
}

func onAcknowledgement(id: String) {
    print("onAcknowledgement: \(id)")
}

func onMessage(message: any self_ios_sdk.Message) {
    print("onMessage: \(message)")
    switch message {
    case is CredentialRequest:
        let credentialRequest = message as! CredentialRequest
        self.handleCredentialRequest(credentialRequest: credentialRequest)
    default:
        print("🎯 ContentView: ❓ Unknown message type: \(type(of: message))")
        break
    }
}

func onDisconnect(errorMessage: String?) {

}

func onError(id: String, errorMessage: String?) {

}

// 2. Show the UI for the request and let users accept or reject it. The UI is rendered by the Self SDK.
self_ios_sdk.MessageView(account: account, message: credentialRequest) { result in
    switch result {
    case .success (let status):
        if status == MessageStatus.accepted.rawValue {
            print("Credential request accepted!")
        } else if status == MessageStatus.rejected.rawValue {
            print("Credential request rejected!")
        }
    case .failure(let error):
        print("Action failed: \(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.