# Email Authentication

Openfort supports two email-based auth flows on iOS, both backed by the same embedded wallet:

* **Email + password** — classic credentials. New accounts can require an email verification step before the wallet is usable.
* **Email OTP** — passwordless. The user receives a one-time code by email and logs in with it. Good for apps that want to avoid passwords entirely.

Pick whichever fits your onboarding; you can also offer both. Every method here returns an [`OFAuthResponse`](/docs/products/embedded-wallet/authentication#response-types) and is `async throws` (a completion-handler variant exists for each).

:::note
All examples assume the SDK is already initialized and ready. If you haven't set that up, follow the [Swift quickstart](/docs/products/embedded-wallet/swift) first — and remember to `await OFSDK.shared.waitUntilReady()` before the first auth call.
:::

## Email + password

### Sign up a new user

`signUpWithEmailPassword` registers the user. You can attach arbitrary metadata (a display name, for example) via `options.data`. If your project requires email verification, the response's `action` is `"verify_email"` — request a verification email and the user finishes via the link.

```swift
import SwiftUI
import OpenfortSwift

@MainActor
final class EmailAuthModel: ObservableObject {
    @Published var email = ""
    @Published var password = ""
    @Published var firstName = ""
    @Published var lastName = ""
    @Published var status = ""

    func signUp() async {
        do {
            let result = try await OFSDK.shared.signUpWithEmailPassword(
                params: OFSignUpWithEmailPasswordParams(
                    email: email,
                    password: password,
                    options: OFSignUpWithEmailPasswordOptionsParams(
                        data: ["name": "\(firstName) \(lastName)"]
                    )
                )
            )

            if result?.action == "verify_email" {
                try await OFSDK.shared.requestEmailVerification(
                    params: OFRequestEmailVerificationParams(
                        email: email,
                        redirectUrl: "myapp://verify" // your app's deep link
                    )
                )
                status = "Verification email sent — check your inbox."
                return
            }

            status = "Signed up. Configure the wallet next."
        } catch {
            status = "Sign-up failed: \(error.localizedDescription)"
        }
    }
}
```

:::tip
`redirectUrl` should be a deep link your app handles (a custom URL scheme like `myapp://verify` or a Universal Link). The verification email opens this URL with a `token` query parameter — pass that token to [`verifyEmail`](#verify-an-email-address) to complete verification in-app.
:::

#### `OFSignUpWithEmailPasswordParams`

```swift
public struct OFSignUpWithEmailPasswordParams: OFCodableSendable {
    public let email: String
    public let password: String
    public let options: OFSignUpWithEmailPasswordOptionsParams? // optional metadata
}

public struct OFSignUpWithEmailPasswordOptionsParams: OFCodableSendable {
    public let data: [String: String]? // e.g. ["name": "Ada Lovelace"]
}
```

### Log in an existing user

```swift
func logIn() async {
    do {
        let result = try await OFSDK.shared.logInWithEmailPassword(
            params: OFLogInWithEmailPasswordParams(email: email, password: password)
        )
        status = "Logged in as \(result?.user?.email ?? "unknown")."
    } catch {
        status = "Login failed: \(error.localizedDescription)"
    }
}
```

#### `OFLogInWithEmailPasswordParams`

```swift
public struct OFLogInWithEmailPasswordParams: OFCodableSendable {
    public let email: String
    public let password: String
}
```

### Verify an email address

When the user returns to your app from the verification link, extract the `token` and confirm it:

```swift
try await OFSDK.shared.verifyEmail(
    params: OFVerifyEmailParams(token: tokenFromRedirect)
)
```

## Email OTP (passwordless)

A two-step flow: request a code, then exchange it for a session. Nothing to store, no passwords to reset.

```swift
@MainActor
final class EmailOtpModel: ObservableObject {
    @Published var email = ""
    @Published var code = ""
    @Published var codeSent = false
    @Published var status = ""

    // Step 1 — email the user a one-time code.
    func sendCode() async {
        do {
            try await OFSDK.shared.requestEmailOtp(
                params: OFRequestEmailOtpParams(email: email)
            )
            codeSent = true
            status = "We emailed you a code."
        } catch {
            status = "Couldn't send code: \(error.localizedDescription)"
        }
    }

    // Step 2 — exchange the code for an authenticated session.
    func verifyCode() async {
        do {
            let result = try await OFSDK.shared.logInWithEmailOtp(
                params: OFLogInWithEmailOtpParams(email: email, otp: code)
            )
            status = "Logged in as \(result?.user?.email ?? "unknown")."
        } catch {
            status = "Invalid or expired code: \(error.localizedDescription)"
        }
    }
}
```

#### Parameters

```swift
public struct OFRequestEmailOtpParams: OFCodableSendable {
    public let email: String
}

public struct OFLogInWithEmailOtpParams: OFCodableSendable {
    public let email: String
    public let otp: String
}
```

## Next steps

After any successful sign-in the user is authenticated but has **no wallet yet** (`OFEmbeddedState.embeddedSignerNotConfigured`). Configure one to start signing:

* [Set up the embedded wallet](/docs/products/embedded-wallet/swift/wallet/recovery) and pick a [recovery method](/docs/configuration/recovery-methods)
* Read [user session & access tokens](/docs/products/embedded-wallet/swift/auth/user-session)
* Let users [reset a forgotten password](/docs/products/embedded-wallet/swift/auth/reset-password)
