PHPackages                             citruslab/totp - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. citruslab/totp

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

citruslab/totp
==============

TOTP generator for PHP

0.9.1(3mo ago)1721[2 PRs](https://github.com/darrenedale/php-totp/pulls)Apache-2.0PHPPHP &gt;=8.2CI failing

Since Apr 12Pushed 3mo ago1 watchersCompare

[ Source](https://github.com/darrenedale/php-totp)[ Packagist](https://packagist.org/packages/citruslab/totp)[ Docs](https://github.com/darrenedale/php-totp)[ RSS](/packages/citruslab-totp/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (9)Dependencies (3)Versions (24)Used By (0)

php-totp
========

[](#php-totp)

[![Conmposer Validation and Unit Tests](https://github.com/darrenedale/php-totp/actions/workflows/php-ci.yml/badge.svg)](https://github.com/darrenedale/php-totp/actions/workflows/php-ci.yml)

Time-based One Time Password Generator for PHP.

Add two-factor authentication to your app using RFC 6238-compliant TOTP, compatible with commonly-available authenticator apps such as Google Authenticator, KeePassXC, Microsoft Authenticator and more.

Quick start
-----------

[](#quick-start)

1. Generate a secure, random secret for your user:

    ```
    use CitrusLab\Totp\Factory;

    $user->totpSecret = Factory::randomSecret()->raw()
    ```
2. Notify the user of the details of their TOTP for them to import into their authenticator app:

    ```
    use CitrusLab\Totp\Factory;
    use CitrusLab\Totp\UrlGenerator;

    $factory = new Factory();
    UrlGenerator::for($user->username)->urlFor($factory->totp(new Secret($user->totpSecret)))
    ```
3. When a user logs in, ask them for their current TOTP and verify it:

    ```
    use CitrusLab\Totp\Factory;

    $factory = new Factory();
    $factory->totp(new Secret($user->totpSecret))->verify($inputOtp)
    ```

Contents
--------

[](#contents)

- [Provisioning TOTP for users](README.md#provisioning-totp-for-users)
    - [Generating secrets](README.md#generating-secrets)
    - [Notifying users](README.md#notifying-users)
    - [Verifying successful provisioning](README.md#verifying-successful-provisioning)
- [Authenticating](README.md#authenticating)
    - [Ensuring OTPs are used only once](README.md#ensuring-otps-are-used-only-once)
- [Custom TOTP Configurations](README.md#custom-totp-configurations)
    - [Hashing algorithms](README.md#hashing-algorithms)
    - [Password digits](README.md#password-digits)
    - [Reference timestamp and time step](README.md#reference-timestamp-and-time-step)
- [Base32/Base64 secrets](README.md#base32base64-secrets)

See also
--------

[](#see-also)

- [Secrets.md](Secrets.md)
- [API.md](API.md)

Introduction
------------

[](#introduction)

TOTP is specified in [RFC 6238](https://www.ietf.org/rfc/rfc6238.txt) and builds on [HMAC-based One-Time Passwords (HOTP, RFC4226)](https://www.ietf.org/rfc/rfc4226.txt) by computing a [Hashed Message Authentication Code (HMAC, RFC 2104)](https://www.ietf.org/rfc/rfc2104.txt) based on a count of the number of time steps that have elapsed since a given point in time and a random secret that is known by the authorising server (your app) and a secure client app (your users' authenticator apps). A 31-bit integer is then derived from the HMAC and the rightmost (usually 6) decimal digits are used as the password (padded with 0s if required). As long as the server and app agree on the current time, the reference time, the size of the time step and the secret, they both calculate the same sequence of passwords at the same time.

The *Totp* library consists of four main components: a `Totp` class, which calculates TOTPs; a `Factory` class that provides access to `Totp` calculator instances; a collection of OTP `Renderer` classes that turn the result of the calculation performed by `Totp` into actual one-time passwords; and an `UrlGenerator` class, which helps generate the information the user needs to set up their authenticator app.

The examples below use notional functions, classes and methods to fill in the functionality that is outside the scope of the *Totp* library. For example, the `encrypt()` function is used as a placeholder for whatever mechanism your app uses to encrypt data. They also assume a standard TOTP setup as described in RFC 6238 - that is, a reference time of 00:00:00 on 01/01/1970, a time step of 30 seconds and the SHA1 hashing algorithm producing 6-digit passwords. Possibilities for customising the TOTP setup are described later.

Provisioning TOTP for Users
---------------------------

[](#provisioning-totp-for-users)

There are three steps involved in provisioning a user with TOTP:

1. [Generate, encrypt and store a secret](README.md#generating-secrets) for the user.
2. [Send the user a notification](README.md#notifying-users) with a URL, secret and/or QR code they can import into their authenticator app.
3. [Verify successful provisioning](README.md#verifying-successful-provisioning) by asking the user for their current OTP.

### Generating secrets

[](#generating-secrets)

The TOTP specification mandates that secrets are generated randomly (i.e. not chosen by the user). You can generate your own secrets, but *Totp* provides a method - `Factory::randomSecret()` that will generate a random secret for you that is guaranteed to be cryptographically secure and strong enough for all the hashing algorithms supported.

*Totp* used a named type to represent secrets to prevent use of invalid secrets and to ensure that the content of the secret is always securely erased when no longer needed.

Once you have generated the secret you must store it securely. It must always be stored encrypted.

```
use CitrusLab\Totp\Factory;

$user->totpSecret = encrypt(Factory::randomSecret());
$user->save();
```

Often, Base32 encoding is used with TOTP secrets, particularly when adding them to an authenticator app. If you need your secret in Base32, *Totp* provides a `Base32` codec class to do the conversion:

```
use CitrusLab\Totp\Factory;

$user->totpSecret = encrypt(Factory::randomSecret()->base32());
$user->save();
```

Sometimes Base64 is also used, for which the `Secret::base64()` method is available.

### Minimising the secret's unencrypted availability

[](#minimising-the-secrets-unencrypted-availability)

You should strive to minimise the time that the shared secret is unencrypted in RAM. Whenever you are using it, whether to provision or to verify, you should only retrieve it just before you are ready to use it, you should discard it as soon as you no longer need it, and you should ensure that the variable containing the secret is securely erased before it is discarded. If you don't do this the unencrypted secret could remain "visible" in memory that is no longer used by your app. The `scrubString()` function in the `\CitrusLab\Totp` namespace is available to achieve this - pass it the string variable containing the secret and it will overwrite the string with random bytes.

All code in the *Totp* library that is intended for use with TOTP secrets scrubs its data in this way to help prevent unexpected visibility of TOTP secrets. You should `unset()` your instances of *Totp* classes once you no longer need them, and ensure that you don't keep unnecessary references.

### Notifying users

[](#notifying-users)

There are three common ways that users are provided with the details of their TOTP secret and most authenticator apps support at least one of them - many support all three.

**1. Just the secret**

The first is simply providing them with the secret. Since the secret is a binary string, it will need to be converted to some kind of text-safe format, and Base32 is usually used for this. This method of notifying users is only viable if the standard TOTP setup is being used - that is 6-digit OTPs, SHA1 hashes, the Unix epoch as the reference time and 30 seconds as the time step. If you are using a custom TOTP setup, you will need to provide more information to your users, and they will need to perform more steps to configure their authenticator app.

```
use CitrusLab\Totp\Codecs\Base32;

$user->notify(Base32::encode(decrypt($user->totpSecret)));
```

**2. An `otpauth` URL**

The second method is to provide your users with a specially constructed URL that their authenticator app can read. The URL format is [described here](https://github.com/google/google-authenticator/wiki/Key-Uri-Format). *Totp* provides a `UrlGenerator` class to create these URLs:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Secret;
use CitrusLab\Totp\UrlGenerator;

$factory = new Factory()
$user->notify(UrlGenerator::from("MyWebApp")->for($user->username)->urlFor($factory->totp(new Secret(decrypt($user->totpSecret))));
```

By default, the UrlGenerator will insert as much information into the generated URL as is necessary to represent your TOTP setup. So if you are using the SHA512 hash algorithm, the generated URL will contain the `algorithm` URL parameter but if you're using the default SHA1 algorithm, the `algorithm` URL parameter will be omitted. The `UrlGenerator` class provides a fluent interface to configure how it constructs the URLs (for example, you can force it to generate the `algorithm` URL parameter regardless of whether you are using a non-default algorithm by chaining the `withAlgorithm()`method before the `urlFor()` method).

This method of notifying supports all custom setups except those that use a non-standard reference time (since there is no URL parameter for specifying it). Many TOTP-capable authenticator apps support URLs of this type, although you will need to check the level of support in the app you are targeting for your users - for example *Google Authenticator*supports URLs but does not recognise the `algorithm` parameter and always uses the SHA1 algorithm.

**3. A QR code**

The third method is to provide users with a QR code that their authenticator app can scan. This is effectively identical to using the URL method above - the QR code is simply a representation of the generated URL.

*Totp* does not (yet) have a QR code generator, but it should be simple to use an existing QR code generator along with the `UrlGenerator` to create QR codes to send to your users. [*bacon/bacon-qr-code*](https://packagist.org/packages/bacon/bacon-qr-code) is one such external library.

### Verifying successful provisioning

[](#verifying-successful-provisioning)

Once a user has been provisioned, you need to ask them for the OTP from their authenticator app to confirm that it has been set up successfully. Once you've received the user's input, verification is simple:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Secret;

use function CitrusLab\Totp\scrubString;

$factory = new Factory()
$isVerified = $factory->totp(new Secret(decrypt($user->totpSecret)))->verify($inputOtp);
scrubString($inputOtp);
```

To avoid problems arising when the user enters their OTP close to the end of a time step, you can choose to accept a small number of previous passwords - typically just one - as well as the current password. Provide a `window`argument to the `Totp::verify()` method, which identifies the maximum number of time steps the verification will go back to check for a matching OTP.

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Secret;

use function CitrusLab\Totp\scrubString;

$factory = new Factory()
$isVerified = $factory->totp(new Secret(decrypt($user->totpSecret)))->verify(password: $inputOtp, window: 1);
scrubString($inputOtp);
```

By default, `Totp::verify()` only accepts the current OTP. **It is very strongly recommended that you verify *at most* with a window of 1**.

### Batch-provisioning users

[](#batch-provisioning-users)

You can re-use an UrlGenerator instance to provision multiple users with TOTP and notify each of them with their own unique URL.

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Secret;
use CitrusLab\Totp\UrlGenerator;

$factory = new Factory()
$generator = UrlGenerator::from("CitrusLab");

foreach ($users as $user) {
   $user->totpSecret = encrypt(Factory::randomSecret());
   $user->save();
   $user->notify($generator->for($user->username)->urlFor($factory->totp(new Secret(decrypt($user->totpSecret)))));
}
```

Authenticating
--------------

[](#authenticating)

Authenticating users' TOTPs is mostly a simple case of asking the user for their current OTP and verifying it. This is identical to verifying the initial setup of their TOTP app:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Secret;

use function CitrusLab\Totp\scrubString;

$factory = new Factory()
$isVerified = $factory->totp(new Secret(decrypt($user->totpSecret)))->verify($inputOtp);
scrubString($inputOtp);
```

Or, with a window of verification:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Secret;

use function CitrusLab\Totp\scrubString;

$factory = new Factory()
$isVerified = $factory->totp(new Secret(decrypt($user->totpSecret)))->verify(password: $inputOtp, window: 1);
scrubString($inputOtp);
```

If `Totp::verify()` returns `false`, the user has not provided the correct OTP and must not be authenticated with your app; if it returns `true` the user has provided a valid OTP and can be authenticated.

### Ensuring OTPs are used only once

[](#ensuring-otps-are-used-only-once)

The RFC mandates that each generated OTP must be used only once to successfully authenticate - once an OTP has been used to successfully authenticate, that OTP must not be used again.

One way to ensure each OTP is never reused is to record the TOTP counter after each successful authentication. The counter is an incrementing integer that indicates how many time steps have passed since the reference time. By recording the highest used counter value and refusing verification of any OTP generated at or before the corresponding time step you can ensure that no OTP can be reused.

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Secret;

use function CitrusLab\Totp\scrubString;

$factory = new Factory()
$totp = $factory->totp(new Secret(decrypt($user->totpSecret)));

if ($user->highestUsedTotpCounter < $totp->counter()) {
    if ($totp->verify($inputOtp)) {
       $user->highestUsedTotpCounter = $totp->counter();
       $user->save();
       // user is authenticated
    } else {
        // incorrect OTP
    }
} else {
    // OTP has already been used
}

// ensure the secret is shredded
scrubString($inputOtp);
unset($totp);
```

You can also use a verification window in the call to `Totp::verify()`, but don't forget to adjust the window to avoid accepting a previously-used OTP:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Secret;

use function CitrusLab\Totp\scrubString;

$factory = new Factory()
$totp = $factory->totp(new Secret(decrypt($user->totpSecret)));
$window = min(1, $totp->counter() - $user->highestUsedTotpCounter - 1);

if (0 verify(password: $inputOtp, window: $window)) {
        ...
    }
}

// ensure the secret is shredded
scrubString($inputOtp);
unset($totp);
```

It is important that you ensure that **all routes to authentication that use the TOTP secret are protected against OTP re-use** - for example if you have a mobile app and a web app, you must ensure that a OTP used to authenticate with the web app cannot subsequently be used to authenticate using the mobile app. [RFC 4226](https://www.ietf.org/rfc/rfc4226.txt) has a good discussion of the reasoning for this.

Custom TOTP configurations
--------------------------

[](#custom-totp-configurations)

There are four things you can customise about your TOTP setup:

1. The hashing algorithm
2. The reference timestamp
3. The size of the time step
4. The number of digits in your OTPs

Customising your TOTP setup should be considered a one-time option. Once you have settled on a setup it is difficult to change it (you'd need to re-provision all your users and they would all need to reconfigure their authenticator apps) so it's usually best to choose your setup carefully before you begin.

Both the `Factory` constructor and the convenience methods `Factory::sixDigits()`, `Factory::eightDigits()` and `Factory::integer()` accept arguments to customise all four aspects of TOTP. All these arguments use the defaults specified in the TOTP RFC unless you explicitly provide a value, which means you can use PHP's named arguments to customise only those aspects of your TOTP instances that are non-default.

### Hashing algorithms

[](#hashing-algorithms)

TOTP supports three hashing algorithms - **SHA1**, **SHA256** and **SHA512**. The strongest is SHA512, while the default specified in the RFC is SHA1 (for compatibility with HOTP). As noted above, you should check that the authenticator apps that you are targeting for your users support the algorithm you are intending to use before customising it.

*Totp* uses a named type for the hash algorithm to prevent use of invalid values. The type provides constants for the supported algorithms, and you're strongly advised to use them when creating your HashAlgorithm instance.

To use SHA256 create your `Factory` instance like this:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\HashAlgorithm;

$factory = new Factory(hashAlgorithm: new HashAlgorithm(HashAlgorithm::Sha256Algorithm));
```

Similarly, to use SHA512:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\HashAlgorithm;

$factory = new Factory(hashAlgorithm: new HashAlgorithm(HashAlgorithm::Sha512Algorithm));
```

### Reference timestamp and time step

[](#reference-timestamp-and-time-step)

The counter that TOTP uses is the number of time steps that have elapsed since the reference time. By default, the reference time is 00:00:00 01/01/1970 (AKA the Unix epoch, or the Unix timestamp `0`). The default time step size is 30 seconds. Unless you have a good reason to change them, these defaults are reasonable choices. If you do choose to customise the time step, bear in mind that very small intervals will make it harder for users since they'll have less time available to enter the correct OTP. Similarly, making the interval too large can also make it difficult for users since you may effectively lock them out for a short period if they log off after only a short session.

*Totp* uses a named type for the time step to prevent use of invalid values.

To use a time step of 60 seconds instead of 30 create your `Factory` instances like this:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\TimeStep;

$factory = new Factory(timeStep: new TimeStep(60));
```

You can customise the reference time using either Unix timestamps:

```
use CitrusLab\Totp\Factory;

$factory = new Factory(referenceTime: 86400);
```

or `DateTime` objects:

```
use CitrusLab\Totp\Factory;

$factory = new Factory(referenceTime: new DateTime("1970-01-02 00:00:00", new DateTimeZone("UTC")));
```

Both of these examples create a TOTP with the reference time set to midnight on January 2nd 1970 UTC. You are strongly encouraged to use the UTC timezone when creating `DateTime` objects to avoid any confusion. The TOTP algorithm works with Unix timestamps that are always measured from 00:00:00, 01/01/9170 UTC.

You can customise both the time step and reference time:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\TimeStep;

$factory = new Factory(timeStep: new TimeStep(60), referenceTime: new DateTime("1970-01-02 00:00:00", new DateTimeZone("UTC")));
```

And also the hash algorithm:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\TimeStep;

$factory = new Factory(
    timeStep: new TimeStep(60),
    referenceTime: new DateTime("1970-01-02 00:00:00", new DateTimeZone("UTC")),
    hashAlgorithm: new HashAlgorithm(HashAlgorithm::Sha512Algorithm),
);
```

### Password digits

[](#password-digits)

The number of digits in OTPs defaults to 6, but can range from 6 to 9 inclusive. There's technically no reason why larger numbers of digits can't be used, but there is nothing to gain other than padding OTPs with 0s to the left.

The easiest way to create a `Factory` that produces `Totp` instances renderering 8-digit passwords is to use the `Factory::eightDigits()` convenience method:

```
use CitrusLab\Totp\Factory;

$factory = Factory::eightDigits();
```

You can, of course, still customise other aspects of your `Totp`:

```
use CitrusLab\Totp\Factory;

$factory = Factory::eightDigits(
    timeStep: new TimeStep(60),
    referenceTime: new DateTime("1970-01-02 00:00:00", new DateTimeZone("UTC")),
    hashAlgorithm: new HashAlgorithm(HashAlgorithm::Sha512Algorithm),
);
```

If you want to use a less common number of digits, use the `Totp::integer()` method. *Totp* uses a named type for the number of digits to prevent use of invalid values.

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Digits;

$factory = Factory::integer(new Digits(9));
```

And again, with more customisation:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Types\Digits;
use CitrusLab\Totp\Types\HashAlgorithm;
use CitrusLab\Totp\Types\TimeStep;

$factory = Factory::integer(
    digits: new Digits(9),
    timeStep: new TimeStep(60),
    referenceTime: new DateTime("1970-01-02 00:00:00", new DateTimeZone("UTC")),
    hashAlgorithm: new HashAlgorithm(HashAlgorithm::Sha512Algorithm),
);
```

For control over passwords beyond just the number of digits they contain, you can provide the `renderer` argument to the constructor. For example, to have your `Totp` instances produce 5-character OTPs that are compatible with the *Steam*authenticator:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Renderers\Steam;

$factory = new Factory(renderer: new Steam());
```

And along with other customisations:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Renderers\Steam;
use CitrusLab\Totp\Types\Digits;
use CitrusLab\Totp\Types\HashAlgorithm;
use CitrusLab\Totp\Types\TimeStep;

$factory = new Factory(
    renderer: new Steam(),
    timeStep: new TimeStep(60),
    referenceTime: new DateTime("1970-01-02 00:00:00", new DateTimeZone("UTC")),
    hashAlgorithm: new HashAlgorithm(HashAlgorithm::Sha512Algorithm),
);
```

Base32/Base64 secrets
---------------------

[](#base32base64-secrets)

As mentioned above, TOTP is commonly used with secrets that are encoded either as Base32 or Base64 text to make them easy to enter into authenticator apps. If you have your secrets stored using one of these encodings (for example in a text field in your database), they will need decoding (as well as decrypting) before being passed to a `Totp` instance.

You can either do this yourself using the provided codec classes `CitrusLab\Totp\Codecs\Base32` and/or `CitrusLab\Totp\Codecs\Base64`:

```
use CitrusLab\Totp\Factory;
use CitrusLab\Totp\Codecs\Base32;
use CitrusLab\Totp\Codecs\Base64;

$factory = new Factory();
$totp = $factory->totp(new Secret(Base32::decode(decrypt($user->totpSecret))));
$totp = $factory->totp(new Secret(Base64::decode(decrypt($user->totpSecret))));
```

Or you can use the convenience methods on the `Secret` class:

```
use CitrusLab\Totp\Factory;

$factory = new Factory();
$totp = $factory->totp(Secret::fromBase32(decrypt($user->totpSecret)));
$totp = $factory->totp(Secret::fromBase64(decrypt($user->totpSecret)));
```

RFCs
----

[](#rfcs)

- H. Krawczyk, M. Bellare &amp; R. Canetti, *[RFC2104: HMAC: Keyed-Hashing for Message Authentication](https://www.ietf.org/rfc/rfc2104.txt)*, , retrieved 17th April, 2022.
- D. M'Raihi, M. Bellare, F. Hoornaert, D. Naccache &amp; O. Ranen, 2005, *[RFC4226: HOTP: An HMAC-Based One-Time Password Algorithm](https://www.ietf.org/rfc/rfc4226.txt)*, , retrieved 17th April, 2022.
- D. M'Raihi, S. Machani, M. Pei &amp; J. Rydell, 2011, *[RFC6238: TOTP: Time-Based One-Time Password Algorithm](https://www.ietf.org/rfc/rfc6238.txt)*, , retrieved 17th April, 2022.

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance81

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity61

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~173 days

Recently: every ~344 days

Total

9

Last Release

98d ago

PHP version history (2 changes)0.0.2PHP &gt;=8.0

0.9.0PHP &gt;=8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/f02407f95953bc62df9f11df5d277e44a50c7bc752d500e1be79168fbd316329?d=identicon)[dedaleus](/maintainers/dedaleus)

---

Top Contributors

[![darrenedale](https://avatars.githubusercontent.com/u/26558174?v=4)](https://github.com/darrenedale "darrenedale (37 commits)")

---

Tags

totpAuthentication2fa

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/citruslab-totp/health.svg)

```
[![Health](https://phpackages.com/badges/citruslab-totp/health.svg)](https://phpackages.com/packages/citruslab-totp)
```

###  Alternatives

[pragmarx/google2fa

A One Time Password Authentication package, compatible with Google Authenticator.

2.0k82.4M163](/packages/pragmarx-google2fa)[pragmarx/google2fa-qrcode

QR Code package for Google2FA

12024.6M37](/packages/pragmarx-google2fa-qrcode)[scheb/2fa-totp

Extends scheb/2fa-bundle with two-factor authentication using TOTP

292.7M21](/packages/scheb-2fa-totp)[scheb/2fa-bundle

A generic interface to implement two-factor authentication in Symfony applications

6914.0M61](/packages/scheb-2fa-bundle)[jiripudil/otp

Library that generates and verifies one-time passwords.

2825.4k1](/packages/jiripudil-otp)[remotemerge/totp-php

Lightweight, fast, and secure TOTP (2FA) authentication library for PHP — battle tested, dependency free, and ready for enterprise integration.

2010.2k](/packages/remotemerge-totp-php)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
