Documentation Index Fetch the complete documentation index at: https://mintlify.com/frol/near-connect-ios/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This guide demonstrates how to implement authentication flows using NEAR Connect. The connectAndSignMessage method combines wallet connection and message signing in a single user approval, perfect for sign-in flows.
This code is extracted from the working SignInAndSignMessageDemoView in the NEAR Connect iOS example app.
Complete Authentication Example
import SwiftUI
import NEARConnect
struct SignInAndSignMessageDemoView : View {
@EnvironmentObject var walletManager: NEARWalletManager
@Environment (\. dismiss ) private var dismiss
var onLog: ((_ action: String , _ params: String , _ output: String , _ isError: Bool ) -> Void ) ?
@State private var message = "Sign in to NEAR Connect Demo"
@State private var recipient = "near-connect-demo.near"
@State private var isProcessing = false
@State private var showError = false
@State private var errorMessage = ""
var body: some View {
NavigationView {
Form {
Section {
Text ( "Connect a wallet and sign a message in a single step. The wallet presents one approval screen for both sign-in and message signing." )
. font (. subheadline )
. foregroundColor (. secondary )
}
Section ( header : Text ( "Message Parameters" )) {
VStack ( alignment : . leading , spacing : 4 ) {
Text ( "Message" )
. font (. caption )
. foregroundColor (. secondary )
TextEditor ( text : $message)
. font (. system (. body , design : . monospaced ))
. frame ( height : 60 )
}
TextField ( "Recipient" , text : $recipient)
. textInputAutocapitalization (. never )
. autocorrectionDisabled ()
}
if walletManager.isSignedIn {
Section {
HStack {
Text ( "Connected as" )
. foregroundColor (. secondary )
Spacer ()
Text (walletManager. currentAccount ? . accountId ?? "" )
. font (. caption )
}
Button ( action : {
walletManager. disconnect ()
}) {
Text ( "Disconnect first to test connect flow" )
. foregroundColor (. red )
}
}
}
Section {
Button ( action : connectAndSign) {
HStack {
Spacer ()
if isProcessing {
ProgressView ()
. padding (. trailing , 8 )
}
Text (isProcessing ? "Connecting..." : "Connect & Sign Message" )
Spacer ()
}
}
. disabled (isProcessing || message. isEmpty || recipient. isEmpty || walletManager. isSignedIn )
}
Section {
Text ( "A random nonce is generated automatically to prevent replay attacks." )
. font (. caption )
. foregroundColor (. secondary )
}
}
. navigationTitle ( "Connect & Sign" )
. navigationBarTitleDisplayMode (. inline )
. toolbar {
ToolbarItem ( placement : . navigationBarTrailing ) {
Button ( "Done" ) { dismiss () }
. disabled (isProcessing)
}
}
. alert ( "Error" , isPresented : $showError) {
Button ( "OK" , role : . cancel ) { }
} message : {
Text (errorMessage)
}
}
}
private func connectAndSign () {
let params = "message: \( message ) , recipient: \( recipient ) "
Task {
isProcessing = true
defer { isProcessing = false }
do {
let signInResult = try await walletManager. connectAndSignMessage (
message : message,
recipient : recipient
)
var output = "account: \( signInResult. account . accountId ) , wallet: \( signInResult. account . walletId ) "
if let pk = signInResult.account.publicKey {
output += ", publicKey: \( pk ) "
}
if let sig = signInResult.signedMessage {
output += " \n signedMessage: \( sig ) "
}
onLog ? ( "connectAndSignMessage" , params, output, false )
} catch {
onLog ? ( "connectAndSignMessage" , params, error. localizedDescription , true )
errorMessage = error. localizedDescription
showError = true
}
}
}
}
Step-by-Step Breakdown
Define Message Parameters
@State private var message = "Sign in to NEAR Connect Demo"
@State private var recipient = "near-connect-demo.near"
Set up the message to sign and the recipient (typically your app’s NEAR account).
Use a descriptive message that clearly explains what the user is signing. This improves security and user trust.
Call connectAndSignMessage
let signInResult = try await walletManager. connectAndSignMessage (
message : message,
recipient : recipient
)
This single method handles both wallet connection and message signing with one user approval.
let account = signInResult. account
print ( "Account ID: \( account. accountId ) " )
print ( "Wallet ID: \( account. walletId ) " )
if let publicKey = account.publicKey {
print ( "Public Key: \( publicKey ) " )
}
Access the connected account details from the result.
Verify the Signed Message
if let signedMessage = signInResult.signedMessage {
print ( "Signed Message: \( signedMessage ) " )
// Send to your backend for verification
}
The signed message can be verified server-side to authenticate the user.
API Reference
connectAndSignMessage
SignInResult
NEARAccount
func connectAndSignMessage (
message : String ,
recipient : String ,
nonce : [ UInt8 ] ? = nil
) async throws -> SignInResult
Connects a wallet and signs a message in one flow. Parameters:
message: The message to sign (typically a sign-in prompt)
recipient: Your app’s NEAR account ID (message recipient)
nonce: Optional nonce bytes (auto-generated if nil)
Returns:
SignInResult containing account info and signed message
Throws:
Error if connection fails or user cancels
A random nonce is automatically generated to prevent replay attacks. You can provide a custom nonce if needed.
struct SignInResult {
let account: NEARAccount
let signedMessage: String ?
}
Contains the authentication result. Properties:
account: The connected NEAR account with accountId, walletId, and publicKey
signedMessage: Base64-encoded signed message (or nil if signing failed)
Example: let result = try await walletManager. connectAndSignMessage (
message : "Sign in" ,
recipient : "app.near"
)
print (result. account . accountId ) // "alice.near"
print (result. account . walletId ) // "my-near-wallet"
print (result. signedMessage ) // Base64 signature
struct NEARAccount {
let accountId: String
let walletId: String
let publicKey: String ?
}
Represents a connected NEAR account. Properties:
accountId: The NEAR account ID (e.g., “alice.near”)
walletId: Identifier of the wallet used to connect
publicKey: The account’s public key (optional)
Authentication Patterns
Basic Sign-In
func signIn () async throws {
let result = try await walletManager. connectAndSignMessage (
message : "Sign in to MyApp" ,
recipient : "myapp.near"
)
// User is now connected and authenticated
print ( "Signed in as: \( result. account . accountId ) " )
}
Sign-In with Backend Verification
func signInWithVerification () async throws {
// Step 1: Connect and sign
let result = try await walletManager. connectAndSignMessage (
message : "Sign in to MyApp at \( Date () ) " ,
recipient : "myapp.near"
)
guard let signature = result.signedMessage else {
throw AuthError. signatureMissing
}
// Step 2: Send to backend for verification
let authToken = try await verifySignature (
accountId : result. account . accountId ,
publicKey : result. account . publicKey ?? "" ,
signature : signature
)
// Step 3: Store auth token
UserDefaults. standard . set (authToken, forKey : "authToken" )
}
private func verifySignature (
accountId : String ,
publicKey : String ,
signature : String
) async throws -> String {
// Call your backend API
let url = URL ( string : "https://api.myapp.com/auth/verify" ) !
var request = URLRequest ( url : url)
request. httpMethod = "POST"
request. setValue ( "application/json" , forHTTPHeaderField : "Content-Type" )
let body = [
"accountId" : accountId,
"publicKey" : publicKey,
"signature" : signature
]
request. httpBody = try JSONSerialization. data ( withJSONObject : body)
let (data, _ ) = try await URLSession. shared . data ( for : request)
let response = try JSONDecoder (). decode (AuthResponse. self , from : data)
return response. token
}
Time-Limited Authentication
func signInWithExpiry () async throws {
let timestamp = Date (). timeIntervalSince1970
let expiryTimestamp = timestamp + ( 60 * 60 ) // 1 hour
let message = """
Sign in to MyApp
Timestamp: \( timestamp )
Expires: \( expiryTimestamp )
"""
let result = try await walletManager. connectAndSignMessage (
message : message,
recipient : "myapp.near"
)
// Store both signature and expiry
UserDefaults. standard . set (result. signedMessage , forKey : "signature" )
UserDefaults. standard . set (expiryTimestamp, forKey : "signatureExpiry" )
}
func signInWithCustomNonce () async throws {
// Generate a nonce (typically from your backend)
let nonce = generateNonce ()
let result = try await walletManager. connectAndSignMessage (
message : "Sign in to MyApp" ,
recipient : "myapp.near" ,
nonce : nonce
)
// Verify with backend using the same nonce
try await verifyWithNonce (
signature : result. signedMessage ?? "" ,
nonce : nonce
)
}
private func generateNonce () -> [ UInt8 ] {
var nonce = [ UInt8 ]( repeating : 0 , count : 32 )
_ = SecRandomCopyBytes (kSecRandomDefault, nonce. count , & nonce)
return nonce
}
User Flow
User taps Connect & Sign Message
The app calls connectAndSignMessage with a descriptive message.
Wallet UI appears
The WalletBridgeSheet shows available NEAR wallets.
User selects wallet
User chooses which wallet to connect (e.g., MyNearWallet, Meteor, etc.).
User approves in wallet
The wallet shows a single approval screen for both connection and message signing.
Wallet signs message
The wallet cryptographically signs the message with the user’s private key.
App receives result
The app receives account details and the signed message for verification.
Security Best Practices
Follow these security practices when implementing authentication:
Include Timestamp in Message
let message = """
Sign in to MyApp
Timestamp: \( Date (). timeIntervalSince1970 )
"""
Prevents old signatures from being reused.
Verify Signatures Server-Side
// NEVER trust client-side verification alone
// Always send the signature to your backend
let isValid = try await backend. verifySignature (
accountId : result. account . accountId ,
signature : result. signedMessage ?? "" ,
publicKey : result. account . publicKey ?? ""
)
Use Descriptive Messages
let message = """
Sign in to MyApp (myapp.near)
Action: Authentication
Timestamp: \( Date () )
This signature will be used to verify your identity.
"""
Clear messages help users understand what they’re signing.
Store Auth State Securely
import Security
func storeAuthToken ( _ token : String ) {
// Use Keychain for sensitive data
let query: [ String : Any ] = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : "authToken" ,
kSecValueData as String : token. data ( using : . utf8 ) !
]
SecItemAdd (query as CFDictionary, nil )
}
Common Use Cases
Wallet-Based Login
Replace traditional username/password with NEAR wallet authentication:
func login () async throws {
let result = try await walletManager. connectAndSignMessage (
message : "Sign in to access your account" ,
recipient : "myapp.near"
)
// User is authenticated by their NEAR account
await loadUserProfile ( accountId : result. account . accountId )
}
Gated Content Access
Verify ownership before showing premium content:
func unlockPremiumContent () async throws {
let result = try await walletManager. connectAndSignMessage (
message : "Verify ownership to access premium content" ,
recipient : "content.near"
)
let hasAccess = try await backend. checkAccess (
accountId : result. account . accountId ,
signature : result. signedMessage ?? ""
)
if hasAccess {
showPremiumContent ()
}
}
Multi-Factor Authentication
Add NEAR wallet as a second authentication factor:
func secondFactorAuth ( username : String ) async throws {
let result = try await walletManager. connectAndSignMessage (
message : "Second factor authentication for \( username ) " ,
recipient : "auth.near"
)
try await backend. verify2FA (
username : username,
accountId : result. account . accountId ,
signature : result. signedMessage ?? ""
)
}
The connectAndSignMessage method is perfect for authentication because it combines connection and signing in a single user action, reducing friction in the sign-in flow.
Next Steps
Token Transfer Send NEAR tokens after authentication
Smart Contracts Call contracts with authenticated user
Error Handling Handle authentication errors
Security Best practices for secure authentication