LNURL Authentication API docs
LNURL-auth allows users to authenticate with services using their Lightning wallet, without requiring passwords or usernames. The Breez SDK supports LNURL-auth following the LUD-04 and LUD-05 specifications.
How it works
LNURL-auth uses cryptographic key derivation to generate domain-specific keys, ensuring that:
- Each service gets a unique authentication key
- Your master key remains private
- Authentication is secure and passwordless
The SDK handles:
- Domain-specific key derivation (LUD-05)
- Challenge signing
- Callback to the LNURL service
Parsing LNURL-Auth URLs
After parsing an LNURL-auth URL, you'll receive an LnurlAuthRequestDetails object containing:
- k1 - The authentication challenge (hex-encoded 32 bytes)
- action - Optional action type:
register,login,link, orauth - domain - The service domain requesting authentication
- url - The callback URL
// LNURL-auth URL from a service
// Can be in the form:
// - lnurl1... (bech32 encoded)
// - https://service.com/lnurl-auth?tag=login&k1=...
let lnurl_auth_url = "lnurl1...";
if let Ok(InputType::LnurlAuth(request_data)) = sdk.parse(lnurl_auth_url).await {
info!("Domain: {}", request_data.domain);
info!("Action: {:?}", request_data.action);
// Show domain to user and ask for confirmation
// This is important for security
}
// LNURL-auth URL from a service
// Can be in the form:
// - lnurl1... (bech32 encoded)
// - https://service.com/lnurl-auth?tag=login&k1=...
let lnurlAuthUrl = "lnurl1..."
if case .lnurlAuth(v1: let requestData) = try await sdk.parse(input: lnurlAuthUrl) {
print("Domain: \(requestData.domain)")
print("Action: \(String(describing: requestData.action))")
// Show domain to user and ask for confirmation
// This is important for security
}
// LNURL-auth URL from a service
// Can be in the form:
// - lnurl1... (bech32 encoded)
// - https://service.com/lnurl-auth?tag=login&k1=...
val lnurlAuthUrl = "lnurl1..."
when (val inputType = sdk.parse(lnurlAuthUrl)) {
is InputType.LnurlAuth -> {
val requestData = inputType.v1
println("Domain: ${requestData.domain}")
println("Action: ${requestData.action}")
// Show domain to user and ask for confirmation
// This is important for security
}
else -> {}
}
// LNURL-auth URL from a service
// Can be in the form:
// - lnurl1... (bech32 encoded)
// - https://service.com/lnurl-auth?tag=login&k1=...
var lnurlAuthUrl = "lnurl1...";
var inputType = await sdk.Parse(lnurlAuthUrl);
if (inputType is InputType.LnurlAuth lnurlAuth)
{
var requestData = lnurlAuth.v1;
Console.WriteLine($"Domain: {requestData.domain}");
Console.WriteLine($"Action: {requestData.action}");
// Show domain to user and ask for confirmation
// This is important for security
}
// LNURL-auth URL from a service
// Can be in the form:
// - lnurl1... (bech32 encoded)
// - https://service.com/lnurl-auth?tag=login&k1=...
const lnurlAuthUrl = 'lnurl1...'
const inputType = await sdk.parse(lnurlAuthUrl)
if (inputType.type === 'lnurlAuth') {
console.log(`Domain: ${inputType.domain}`)
console.log(`Action: ${inputType.action}`)
// Show domain to user and ask for confirmation
// This is important for security
}
// LNURL-auth URL from a service
// Can be in the form:
// - lnurl1... (bech32 encoded)
// - https://service.com/lnurl-auth?tag=login&k1=...
const lnurlAuthUrl = 'lnurl1...'
const inputType = await sdk.parse(lnurlAuthUrl)
if (inputType.tag === InputType_Tags.LnurlAuth) {
const requestData = inputType.inner[0]
console.log(`Domain: ${requestData.domain}`)
console.log(`Action: ${requestData.action}`)
// Show domain to user and ask for confirmation
// This is important for security
}
// LNURL-auth URL from a service
// Can be in the form:
// - lnurl1... (bech32 encoded)
// - https://service.com/lnurl-auth?tag=login&k1=...
String lnurlAuthUrl = "lnurl1...";
InputType inputType = await sdk.parse(input: lnurlAuthUrl);
if (inputType is InputType_LnurlAuth) {
LnurlAuthRequestDetails requestData = inputType.field0;
print("Domain: ${requestData.domain}");
print("Action: ${requestData.action}");
// Show domain to user and ask for confirmation
// This is important for security
}
# LNURL-auth URL from a service
# Can be in the form:
# - lnurl1... (bech32 encoded)
# - https://service.com/lnurl-auth?tag=login&k1=...
lnurl_auth_url = "lnurl1..."
try:
input_type = await sdk.parse(lnurl_auth_url)
if isinstance(input_type, InputType.LNURL_AUTH):
request_data = input_type[0]
logging.debug(f"Domain: {request_data.domain}")
logging.debug(f"Action: {request_data.action}")
# Show domain to user and ask for confirmation
# This is important for security
except Exception as error:
logging.error(error)
raise
// LNURL-auth URL from a service
// Can be in the form:
// - lnurl1... (bech32 encoded)
// - https://service.com/lnurl-auth?tag=login&k1=...
lnurlAuthUrl := "lnurl1..."
inputType, err := sdk.Parse(lnurlAuthUrl)
if err == nil {
if lnurlAuth, ok := inputType.(breez_sdk_spark.InputTypeLnurlAuth); ok {
requestData := lnurlAuth.Field0
log.Printf("Domain: %s", requestData.Domain)
log.Printf("Action: %v", requestData.Action)
// Show domain to user and ask for confirmation
// This is important for security
}
}
Performing Authentication
Once you have the authentication request details, you can perform the authentication by passing the request to the lnurl_authlnurl_authlnurlAuthlnurlAuthlnurlAuthlnurlAuthlnurlAuthLnurlAuthLnurlAuth method. The SDK will:
- Derive a domain-specific key pair
- Sign the challenge with the derived key
- Send the signature and public key to the service
// Perform LNURL authentication
let result = sdk.lnurl_auth(request_data).await?;
match result {
LnurlCallbackStatus::Ok => {
info!("Authentication successful");
}
LnurlCallbackStatus::ErrorStatus { error_details } => {
info!("Authentication failed: {}", error_details.reason);
}
}
// Perform LNURL authentication
let result = try await sdk.lnurlAuth(requestData: requestData)
switch result {
case .ok:
print("Authentication successful")
case .errorStatus(errorDetails: let errorDetails):
print("Authentication failed: \(errorDetails.reason)")
}
// Perform LNURL authentication
when (val result = sdk.lnurlAuth(requestData)) {
is LnurlCallbackStatus.Ok -> {
println("Authentication successful")
}
is LnurlCallbackStatus.ErrorStatus -> {
println("Authentication failed: ${result.errorDetails.reason}")
}
}
// Perform LNURL authentication
var result = await sdk.LnurlAuth(requestData);
if (result is LnurlCallbackStatus.Ok)
{
Console.WriteLine("Authentication successful");
}
else if (result is LnurlCallbackStatus.ErrorStatus errorStatus)
{
Console.WriteLine($"Authentication failed: {errorStatus.errorDetails.reason}");
}
// Perform LNURL authentication
const result = await sdk.lnurlAuth(requestData)
if (result.type === 'ok') {
console.log('Authentication successful')
} else if (result.type === 'errorStatus') {
console.log(`Authentication failed: ${result.errorDetails.reason}`)
}
// Perform LNURL authentication
const result = await sdk.lnurlAuth(requestData)
if (result.tag === LnurlCallbackStatus_Tags.Ok) {
console.log('Authentication successful')
} else if (result.tag === LnurlCallbackStatus_Tags.ErrorStatus) {
console.log(`Authentication failed: ${result.inner.errorDetails.reason}`)
}
// Perform LNURL authentication
LnurlCallbackStatus result = await sdk.lnurlAuth(requestData: requestData);
if (result is LnurlCallbackStatus_Ok) {
print("Authentication successful");
} else if (result is LnurlCallbackStatus_ErrorStatus) {
print("Authentication failed: ${result.errorDetails.reason}");
}
# Perform LNURL authentication
try:
result = await sdk.lnurl_auth(request_data=request_data)
if isinstance(result, LnurlCallbackStatus.OK):
logging.debug("Authentication successful")
elif isinstance(result, LnurlCallbackStatus.ERROR_STATUS):
logging.debug(f"Authentication failed: {result.error_details.reason}")
except Exception as error:
logging.error(error)
raise
// Perform LNURL authentication
result, err := sdk.LnurlAuth(requestData)
if err != nil {
var sdkErr *breez_sdk_spark.SdkError
if errors.As(err, &sdkErr) {
// Handle SdkError - can inspect specific variants if needed
// e.g., switch on sdkErr variant for InsufficientFunds, NetworkError, etc.
}
log.Printf("Authentication error: %v", err)
return
}
switch v := result.(type) {
case breez_sdk_spark.LnurlCallbackStatusOk:
log.Println("Authentication successful")
case breez_sdk_spark.LnurlCallbackStatusErrorStatus:
log.Printf("Authentication failed: %s", v.ErrorDetails.Reason)
}
Developer note
The SDK automatically derives domain-specific keys according to LUD-05, ensuring that each service gets a unique linking key. This protects user privacy by preventing services from correlating user identities across different domains.Action Types
LNURL-auth supports different action types that indicate the purpose of the authentication:
- register - Create a new account
- login - Sign in to an existing account
- link - Link the Lightning wallet to an existing account
- auth - Generic authentication
Your application can use the action field to provide appropriate UI feedback to users.
Security Considerations
- Always verify the domain before authenticating
- Show the domain to users for confirmation
- The SDK derives unique keys per domain to prevent tracking
- Authentication keys cannot be used to access funds