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:
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.
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.
varcredentialRequest:CredentialRequest? =nullaccount.setOnRequestListener{msg->when(msg){isCredentialRequest->{if(msg.details().any{it.types().contains(CredentialType.Liveness)&&it.subject()==Constants.SUBJECT_SOURCE_IMAGE_HASH}){credentialRequest=msgnavController.navigate("livenessRoute")// launch liveness UI flow}}}}// integrate Liveness UI flow into NavigationaddLivenessCheckRoute(navController,route="livenessRoute",selfModifier=selfModifier,account={account},withCredential=true,onFinish={selfie,credentials->if(credentialRequest!=null){// after receiving the liveness credentials, send the responsesendCredentialResponse(credentials)}})funsendCredentialResponse(credentials:List<Credential>){valcredentialResponse=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 listeneraccount.setOnRequestListener{[weakself]requestMessageinguardletself=selfelse{return}ifletcredentialRequest=requestMessageas?CredentialRequest{Task{@MainActorinawaitself.handleCredentialRequest(credentialRequest)}}}// 2. Handle credential request with proper filtering and thread safetyprivatefunchandleCredentialRequest(_credentialRequest:CredentialRequest)async{letallClaims:[Claim]=credentialRequest.details()letlivenessCredentials=allClaims.filter{$0.types().contains(CredentialType.Liveness)&&$0.types().contains(CredentialType.Verifiable)}letotherCredentials=allClaims.filter{!($0.types().contains(CredentialType.Liveness)&&$0.types().contains(CredentialType.Verifiable))}// Log other credential types for debuggingforclaiminotherCredentials{print("Ignoring unsupported credential types: \(claim.types())")}if!livenessCredentials.isEmpty{awaithandleLivenessAuthentication(credentialRequest,livenessCredentials:livenessCredentials)}else{print("No liveness credentials found")}}// 3. Handle liveness authentication with proper thread managementprivatefunchandleLivenessAuthentication(_credentialRequest:CredentialRequest,livenessCredentials:[Claim])async{// Analyze liveness requirements from filtered credentialsforclaiminlivenessCredentials{letclaimTypes=claim.types()letsubject=claim.subject()letcomparisonOperator=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){[weakself]selfieImageData,credentials,erroringuardletself=selfelse{return}ifleterror=error{print("Liveness check failed: \(error)")return}guard!credentials.isEmptyelse{print("No credentials provided from liveness check")return}// Send credential responseTask{@MainActorinawaitself.sendCredentialResponse(to:credentialRequest,credentials:credentials)}}}// Response to the above credential request// 4. Send credential response with proper type conversionprivatefuncsendCredentialResponse(torequest:CredentialRequest,credentials:[Any])async{// Convert credentials to proper type (critical for Swift type safety)letselfCredentials=credentials.compactMap{$0as?Credential}guard!selfCredentials.isEmptyelse{print("No valid credentials to send")return}do{letresponse=CredentialResponse.Builder().withRequestId(request.id()).withTypes(request.types()).toIdentifier(request.toIdentifier()).withStatus(ResponseStatus.accepted).withCredentials(selfCredentials).build()tryawaitaccount.send(message:response){messageId,errorinifleterror=error{print("Response failed: \(error)")}else{print("Credentials sent successfully")}}}catch{print("Error building credential response: \(error)")}}
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:=rangepresentation.Credentials(){ifcredential.CredentialType()[0]=="VerifiableCredential"&&credential.CredentialType()[1]=="LivenessCredential"{err=credential.Validate()iferr!=nil{log.Printf("WARN: failed to validate credential - error: %v",err)continue}ifcredential.ValidFrom().After(time.Now()){log.Println("WARN: credential is intended to be used in the future")continue}claims,err:=credential.CredentialSubjectClaims()iferr!=nil{log.Printf("WARN: failed to parse credential claims - error: %v",err)continue}fork,v:=rangeclaims{log.Printf("INFO: credential value - credentialType: %s, field: %s, value: %v",credential.CredentialType(),k,v)}}}
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.