diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..8fb29a7
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @MincDev
\ No newline at end of file
diff --git a/.github/SECURITY.md b/.github/SECURITY.md
new file mode 100644
index 0000000..4e1ae49
--- /dev/null
+++ b/.github/SECURITY.md
@@ -0,0 +1,20 @@
+# Security Policy
+
+## Reporting a Vulnerability
+
+If you discover a security vulnerability in this project, please do **not** open a public GitHub issue.
+
+Instead, report it privately by emailing:
+
+**christopher@mincdevelopment.co.za**
+
+Please include:
+
+- A description of the vulnerability
+- Steps to reproduce it
+- The potential impact
+- Any suggested fix (if available)
+
+I will acknowledge receipt as soon as possible and work to resolve verified issues promptly.
+
+Thank you for helping keep this project secure.
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..3ab07e9
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,26 @@
+version: 2
+
+updates:
+ - package-ecosystem: "composer"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+
+ open-pull-requests-limit: 2
+
+ commit-message:
+ prefix: "deps"
+
+ labels:
+ - "dependencies"
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+
+ commit-message:
+ prefix: "ci"
+
+ labels:
+ - "github-actions"
\ No newline at end of file
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
new file mode 100644
index 0000000..657b56a
--- /dev/null
+++ b/.github/workflows/run-tests.yml
@@ -0,0 +1,35 @@
+name: Run Tests
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+
+jobs:
+ phpunit:
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ php: ['8.2', '8.3', '8.4']
+
+ name: PHP ${{ matrix.php }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+
+ - name: Validate composer.json
+ run: composer validate --strict
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress
+
+ - name: Run PHPUnit
+ run: vendor/bin/phpunit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 55940e5..061e2fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,29 @@
+# Composer
/vendor/
-composer.lock
\ No newline at end of file
+
+# Lock file (libraries typically don't commit this)
+composer.lock
+
+# PHPUnit
+.phpunit.cache/
+
+# IDEs
+.idea/
+.vscode/
+
+# macOS
+.DS_Store
+
+# Windows
+Thumbs.db
+
+# Coverage reports
+coverage/
+coverage.xml
+
+# PHPUnit reports
+testdox.html
+testdox.txt
+
+# Xdebug profiling
+cachegrind.out.*
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
index aaf779e..a8dbb49 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021 MincDev
+Copyright (c) 2021-2026 MincDev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index d9c1d5f..6c0334f 100644
--- a/README.md
+++ b/README.md
@@ -1,44 +1,86 @@
-# php-otpauth
+# PHP-OtpAuth
- A library for generating a 2 factor authentication QR code to use with Google Authenticator, Authy, etc.
-
-### Secure QRCode Creation
+[](https://github.com/mincdev/php-otpauth/actions/workflows/run-tests.yml)
-This library has secure QRCode creation because of the fact that the QRCode is generated locally on your server. This means that the user's secret is not passed to any third party or remote server in order to generate a code. This was inspired by the stack overflow answer by **kravietz** as seen [here](https://stackoverflow.com/a/56737468/3948544)
+A lightweight PHP library for generating and validating **Time-based One-Time Passwords (TOTP)** compatible with Google Authenticator, Microsoft Authenticator, Authy, 1Password, Bitwarden, and other RFC 6238 compatible authenticator applications.
-### Installation (Composer)
+## Features
-```
+- 🔒 Generate cryptographically secure TOTP secrets
+- 📱 Generate QR codes for easy authenticator app provisioning
+- ✅ Validate one-time passwords
+- 🧪 Verified against the official **RFC 6238** test vectors
+- 🖥️ QR codes are generated locally on your server — no secrets are sent to third-party services
+- 🚀 Lightweight with minimal dependencies
+
+## Installation
+
+Install the package using Composer:
+
+```bash
composer require mincdev/php-otpauth
```
-### Dependencies
+## Generating a Secret and QR Code
-This library requires the **tc-lib-barcode** library found at https://github.com/tecnickcom/tc-lib-barcode.
+```php
+use MincDev\OtpAuth\OtpAuthenticator;
-**Note:** The tc-lib-barcode library is maintained and owned by a separate entity.
+$otp = new OtpAuthenticator();
-#### Generating a QR Code
+// Store the secret securely for the user.
+$secret = $otp->newSecret();
-You can generate a QR code which can be scanned by Google Authenticator, Authy, etc. by using the below.
+$qrCode = $otp->getQR(
+ 'john.doe@example.com',
+ 'My Awesome App',
+ $secret
+);
+```
-```php
-$otpAuth = new OtpAuthenticator();
+The returned value is a Base64 data URI that can be used directly in an image tag:
+
+```html
+
+```
-$userName = "MrDoe";
-$appName = "My Awesome App";
+## Validating a Code
+
+```php
+use MincDev\OtpAuth\OtpAuthenticator;
-// Store this secret somewhere safe, as you'll need it to validate the pin later
-$userSecret = $otpAuth->newSecret();
+$otp = new OtpAuthenticator();
-$qrBase64 = $otpAuth->getQR($userName, $appName, $userSecret);
+$isValid = $otp->validate($secret, $userEnteredCode);
```
-#### Validating a PIN
+## Security
-Once your user logs in, you can validate their pin by making use of the following:
+This library generates QR codes **entirely on your server**. The user's secret is never transmitted to an external QR code generation service, ensuring that sensitive authentication data remains under your control.
-```php
-$otpAuth = new OtpAuthenticator();
-$isValid = $otpAuth->validate($userSecret, $pinCode);
+QR code generation is powered by `tc-lib-barcode`.
+
+## Compatibility
+
+This library implements **RFC 6238 (TOTP)** and works with applications such as:
+
+- Google Authenticator
+- Microsoft Authenticator
+- Authy
+- 1Password
+- Bitwarden
+- and other compatible authenticator apps.
+
+## Testing
+
+The TOTP implementation is verified against the official **RFC 6238** test vectors to ensure standards-compliant code generation.
+
+Run the test suite with:
+
+```bash
+composer test
```
+
+## License
+
+Released under the MIT License.
\ No newline at end of file
diff --git a/composer.json b/composer.json
index 0ea97ab..789d5f0 100644
--- a/composer.json
+++ b/composer.json
@@ -1,10 +1,18 @@
{
"name": "mincdev/php-otpauth",
- "description": "A library for genrating a 2 factor authentication QR code",
+ "description": "PHP library for Time-based One-Time Password (TOTP) authentication compatible with Google Authenticator, Microsoft Authenticator and Authy.",
"type": "library",
"license": "MIT",
- "version": "1.0.1",
"minimum-stability": "stable",
+ "keywords": [
+ "totp",
+ "otp",
+ "2fa",
+ "two-factor",
+ "google-authenticator",
+ "authenticator",
+ "security"
+ ],
"authors": [
{
"name": "Christopher Smit",
@@ -12,11 +20,19 @@
}
],
"require": {
- "tecnickcom/tc-lib-barcode": "^1.17"
+ "php": "^8.2 || ^8.3 || ^8.4",
+ "tecnickcom/tc-lib-barcode": "^2.11"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
},
"autoload": {
"psr-4": {
- "MincDev\\OtpAuth\\": "src/"
+ "MincDev\\OtpAuth\\": "src/",
+ "MincDev\\OtpAuth\\Tests\\": "tests/"
}
+ },
+ "scripts": {
+ "test": "phpunit"
}
-}
+}
\ No newline at end of file
diff --git a/example/example.php b/example/example.php
index 1074b64..821db94 100644
--- a/example/example.php
+++ b/example/example.php
@@ -1,34 +1,121 @@
getQR("John Doe", "Some Site", $authCode->newSecret());
+$error = null;
+$isValid = null;
-echo "
+try {
+ $_SESSION['secret'] ??= $auth->newSecret();
+ $secret = $_SESSION['secret'];
+ $qrCode = $auth->getQR('John Doe', 'Some Site', $secret);
+
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $code = trim($_POST['code'] ?? '');
+ $isValid = $auth->validate($secret, $code);
+ }
+} catch (Throwable $e) {
+ $error = $e->getMessage();
+}
+?>
-
-