> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# verifyTotp

> Submits a two-factor code for a sign-in that returned .totpRequired and returns the completed session.

**Function Signature**

```swift theme={null}
public func verifyTotp(_ code: String, userJwt: String) async throws -> AuthenticatedResult
```

A `PortalAuth` method. Completes a sign-in that `handleRedirect` or `signInWith*` left at `.totpRequired`. On success the session is saved to the Keychain before it is returned.

**Parameters**

* **`code`**: The six-digit code from the user's authenticator app. Posted exactly as given, with no trimming or length check, so validate the input before submitting.
* **`userJwt`**: The `userJwt` from the `TotpRequiredResult`, passed back verbatim. Short-lived (ten minutes) and never persisted by the SDK.

**Returns**

* **`AuthenticatedResult`**: Contains the following:
  * **`session`**: `PortalSession`, already persisted. Pass it to `Portal(credentials:)`.
  * **`clientId`**: `String?`, the Portal Client this session belongs to.
  * **`isAccountAbstracted`**: `Bool?`, whether that client uses gas sponsorship.

**Throws**

* `PortalAuthError.invalidUserJwt(detail:)`: `userJwt` cannot be read (for example it is blank or carries no `endUserId`). Raised before the network call, so a bad JWT never costs the user a live code. Restart the sign-in.
* `PortalRequestsError.unauthorized`: The code was wrong, or the `userJwt` has expired or already been used. The SDK cannot tell which. A wrong code does not consume the JWT, so let the user try again with the same `TotpRequiredResult`; if that keeps failing, start a new sign-in.
* `PortalAuthError.malformedResponse(path:missing:)`: The response carried no `clientSessionToken`.
* `PortalAuthError.sessionStorageFailure(message:)`: The code was accepted but the session could not be saved to the Keychain. Call `verifyTotp` again with the same `userJwt`: the SDK finishes the write and returns the session without a network call.
* Other transport errors unchanged.

**Enrollment helpers**

On a user's first sign-in, `TotpRequiredResult.totpLink` carries an `otpauth://` URI the user needs to enroll an authenticator app. The SDK provides:

```swift theme={null}
public extension TotpRequiredResult {
    var totpSecret: String? { get }                          // the base32 secret, for manual entry
    func qrCodeImage(scale: CGFloat = 10) throws -> UIImage  // a scannable QR code
}

public func portalTotpQrCodeImage(otpAuthUrl: String, scale: CGFloat = 10) throws -> UIImage
```

`totpSecret` is `nil`, and `qrCodeImage` throws `PortalAuthError.totpQrUnavailable`, when `totpLink` is missing, is not an `otpauth://` URI, or carries no valid secret. `totpLink` is `nil` for a user who is already enrolled.

**Notes**

* Nothing is persisted until `verifyTotp` succeeds. If the JWT expires or the app is killed first, the user signs in again from the beginning.
* The `endUserId` of the resulting session is read from the `userJwt`; the TOTP endpoint does not return it.
* After success, a redirect re-delivered for the same grant replays as this `.authenticated` result rather than the TOTP step.
* Never log or persist `userJwt`, `totpLink` or `totpSecret`; `totpLink` embeds the TOTP secret. `TotpRequiredResult` redacts both from `description` and `dump`, so printing the value itself is safe. If you offer a "Copy key" button, use `UIPasteboard.general.setItems(_:options:)` with `.localOnly: true` and a short `.expirationDate`.
* A user who loses their authenticator can be reset from the dashboard; see [End users](../../../resources/authentication/end-users).
* Available starting from SDK version 8.0.0.

**Example Usage**

```swift theme={null}
import UIKit
import PortalSwift

func presentTotpPrompt(_ step: TotpRequiredResult) {
    if step.totpLink != nil {
        do {
            qrImageView.image = try step.qrCodeImage()
        } catch {
            print("Error rendering the enrollment QR code: \(error)")
        }
        secretLabel.text = step.totpSecret
    }
}

func submitTotp(code: String, step: TotpRequiredResult) async {
    do {
        let authenticated = try await auth.verifyTotp(code, userJwt: step.userJwt)
        let portal = try Portal(credentials: authenticated.session)
        portal.onSessionInvalidated { [weak self] in
            self?.showSignIn()
        }
        self.portal = portal
    } catch PortalRequestsError.unauthorized {
        print("Wrong code. Try again.")
    } catch PortalAuthError.invalidUserJwt(let detail) {
        print("Restart the sign-in: \(detail)")
    } catch {
        print("Error verifying the code: \(error)")
    }
}
```

**Related Documentation**

* [handleRedirect reference](./portalauthhandleredirect)
* [Handle two-factor authentication](../guide/client-auth#handle-two-factor-authentication)
* [Two-factor authentication](../../../resources/authentication/two-factor-authentication)
