> ## 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

> Complete a sign-in that requires a TOTP code from the user's authenticator app.

## Function Signature

```dart theme={null}
Future<PortalAuthenticated> verifyTotp(
  String code, {
  required String userJwt,
})
```

## Description

A [`PortalAuth`](./portalauth) method. Completes a sign-in that [`handleRedirect`](./portalauthhandleredirect) returned as `PortalTotpRequired`, using the code from the user's authenticator app. The session is persisted on the device before this returns.

A wrong code doesn't use up `userJwt`, so after `AUTH_TOTP_REJECTED` prompt again with the same `userJwt`. An expired `userJwt` is rejected the same way and can't be refreshed: if codes keep being rejected, restart the sign-in.

Available starting from `portal_flutter` 2.0.0.

## Parameters

| Parameter | Type | Required | Description |
| - | - | - | - |
| `code` | `String` | Yes | The code from the authenticator app. Trimmed, and must be at least six digits; an environment can enroll eight-digit codes. |
| `userJwt` | `String` | Yes | `PortalTotpRequired.userJwt`, passed back verbatim. |

## Returns

**`PortalAuthenticated`** - The completed sign-in, the same as the non-TOTP path of `handleRedirect`. Pass `session` to `Portal().initialize(session:)`.

## Example

```dart theme={null}
import 'package:portal_flutter/portal_flutter.dart';

Future<PortalSession?> submitCode(PortalTotpRequired challenge, String code) async {
  try {
    final result = await PortalAuth().verifyTotp(code, userJwt: challenge.userJwt);
    return result.session;
  } on PortalException catch (e) {
    if (e.code == 'AUTH_TOTP_REJECTED') {
      print('Code rejected. Try the next code from your authenticator app.');
    } else {
      print('Verification failed (${e.code}): ${e.message}');
    }
    return null;
  }
}
```

## Errors

| Code | Description |
| - | - |
| `AUTH_INVALID_ARGUMENT` | `code` is not at least six digits. Thrown before any network call |
| `AUTH_NOT_CONFIGURED` | Called before [`configure`](./portalauthconfigure) |
| `AUTH_INVALID_USER_JWT` | `userJwt` is not one from a `PortalTotpRequired`. Thrown before any network call |
| `AUTH_TOTP_REJECTED` | A wrong code or an expired `userJwt`; the backend doesn't say which |
| `AUTH_RATE_LIMITED` | Too many attempts |
| `AUTH_SESSION_STORAGE_FAILURE` | The session could not be saved to secure storage. On iOS, retry with the same `userJwt`; on Android, start a new sign-in. See [Handle errors](../guide/client-auth#handle-errors) |
| `AUTH_VERIFY_TOTP_ERROR` | A network or server error |

## Related

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.