PHP code example of zenstruck / signed-url-bundle

1. Go to this page and download the library: Download zenstruck/signed-url-bundle library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

zenstruck / signed-url-bundle example snippets


use Zenstruck\SignedUrl\Generator;

public function sendPasswordResetEmail(User $user, Generator $generator)
{
    $resetUrl = $generator->build('password_reset_route', ['id' => $user->getId()])
        ->expires('+1 day')
        ->singleUse($user->getPassword())
    ;

    // create email with $resetUrl and send
}

use Zenstruck\SignedUrl\Verifier;
use Zenstruck\SignedUrl\Exception\UrlVerificationFailed;

public function resetPasswordAction(User $user, Verifier $urlVerifier)
{
    try {
        $urlVerifier->verifyCurrentRequest(singleUseToken: $user->getPassword());
    } catch (UrlVerificationFailed $e) {
        $this->flashError($e->messageKey()); // safe reason to show user

        return $this->redirect(...);
    }

    // continue
}

use Zenstruck\SignedUrl\Generator;

/** @var Generator $generator */

$generator->generate('route1'); // http://example.com/route1?_hash=...
$generator->generate('route2', ['parameter1' => 'value']); // http://example.com/route2/value?_hash=...
$generator->generate('route3', [], Generator::ABSOLUTE_PATH); // /route2/value?_hash=...

/** @var Zenstruck\SignedUrl\Generator $generator */

(string) $generator->build('reset_password', ['id' => $user->getId()])
    ->expires('+1 hour')
    ->singleUse($user->getPassword())
;

/** @var Zenstruck\SignedUrl\Generator $generator */

$signedUrl = $generator->build('reset_password', ['id' => $user->getId()])
    ->expires('+1 hour')
    ->singleUse($user->getPassword())
    ->create()
;

/** @var Zenstruck\SignedUrl $signedUrl */
(string) $signedUrl; // the actual URL
$signedUrl->expiresAt(); // \DateTimeImmutable
$signedUrl->isTemporary(); // true
$signedUrl->isSingleUse(); // true

/** @var Zenstruck\SignedUrl\Generator $generator */

(string) $generator->build('route1')->expires('+1 hour'); // http://example.com/route1?__expires=...&_hash=...
(string) $generator->build('route2', ['parameter1' => 'value'])->expires('+1 hour'); // http://example.com/route2/value?__expires=...&_hash=...

// use # of seconds
(string) $generator->build('route1')->expires(3600); // http://example.com/route2/value?__expires=...&_hash=...

// use an explicit \DateTime
(string) $generator->build('route1')->expires(new \DateTime('+1 hour')); // http://example.com/route2/value?__expires=...&_hash=...

use Zenstruck\SignedUrl\Exception\UrlVerificationFailed;

/** @var Zenstruck\SignedUrl\Verifier $verifier */
/** @var string $url */
/** @var Symfony\Component\HttpFoundation\Request $request */

// simple usage: return true if valid and non-expired (if applicable), false otherwise
$verifier->isVerified($url);
$verifier->isVerified($request); // can pass Symfony request object
$verifier->isCurrentRequestVerified(); // verifies the current request (fetched from RequestStack)

// try/catch usage: catch exceptions to provide better feedback to users
try {
    $verifier->verify($url);
    $verifier->verify($request); // alternative
    $verifier->verifyCurrentRequest(); // alternative
} catch (UrlVerificationFailed $e) {
    $e->url(); // the url used
    $e->getMessage(); // Internal message (ie for logging)
    $e->messageKey(); // Safe message with reason to show the user (or use with translator)
}

/** @var Zenstruck\SignedUrl\Generator $generator */

// !! This will be the single-use token that changes once "used" !!
$password = $user->getPassword();

$url = $generator->build('reset_password', ['id' => $user->getId()])
    ->singleUse($password)
    ->create()
;

use Zenstruck\SignedUrl\Exception\UrlVerificationFailed;

/** @var Zenstruck\SignedUrl\Verifier $verifier */
/** @var string $url */
/** @var Symfony\Component\HttpFoundation\Request $request */

// !! This is the single-use token. If the url was generated with a different password verification will fail !!
$password = $user->getPassword();

$verifier->isVerified($url, $password);
$verifier->isVerified($request, $password);
$verifier->isCurrentRequestVerified($password);

// try/catch usage: catch exceptions to provide better feedback to users
try {
    $verifier->verify($url, $password);
    $verifier->verify($request, $password); // alternative
    $verifier->verifyCurrentRequest($password); // alternative
} catch (UrlVerificationFailed $e) {
    $e->messageKey(); // "URL has already been used." (if failed for this reason)
}

final class ResetPasswordToken
{
    public function __construct(private User $user) {}

    public function __toString(): string
    {
        return $this->user->getPassword();
    }
}

/** @var Zenstruck\SignedUrl\Generator $generator */

$generator->build('reset_password', ['id' => $user->getId()])->singleUse(new ResetPasswordToken($user));

/** @var Zenstruck\SignedUrl\Verifier $verifier */

$verifier->isVerified($url, new ResetPasswordToken($user));
$verifier->verify($url, new ResetPasswordToken($user));
$verifier->isCurrentRequestVerified(new ResetPasswordToken($user));
$verifier->verifyCurrentRequest(new ResetPasswordToken($user));

use Zenstruck\SignedUrl\Attribute\Signed;

#[Signed]
#[Route(...)]
public function action1() {} // throws a 403 HttpException if verification fails

#[Signed(status: 404)]
#[Route(...)]
public function action1() {} // throw a 404 exception instead

use Zenstruck\SignedUrl\Exception\UrlVerificationFailed;
use Zenstruck\SignedUrl\Exception\UrlHasExpired;
use Zenstruck\SignedUrl\Exception\UrlAlreadyUsed;

/** @var Zenstruck\SignedUrl\Verifier $verifier */

try {
    $verifier->verifyCurrentRequest($user->getPassword());
} catch (UrlHasExpired $e) {
    // this exception makes the expiration available
    $e->expiredAt(); // \DateTimeImmutable
    $e->messageKey(); // "URL has expired."
    $e->url(); // the URL that failed verification
} catch (UrlAlreadyUsed $e) {
    $e->messageKey(); // "URL has already been used."
    $e->url(); // the URL that failed verification
} catch (UrlVerificationFailed $e) {
    // must be last as a "catch all"
    $e->messageKey(); // "URL Verification failed."
    $e->url(); // the URL that failed verification
}

/** @var \Zenstruck\SignedUrl\Generator $generator */
/** @var \Zenstruck\SignedUrl\Verifier $verifier */

// REQUEST PASSWORD RESET ACTION (GENERATE URL)
$url = $generator->build('reset_password', ['id' => $user->getId()])
    ->expires('+1 day')
    ->singleUse($user->getPassword()) // current password is the token that changes once "used"
    ->create()
;

// send email to user with $url

// PASSWORD RESET ACTION (VERIFY URL)
try {
    $verifier->verifyCurrentRequest($user->getPassword()); // current password as the token
} catch (\Zenstruck\SignedUrl\Exception\UrlVerificationFailed $e) {
    $this->flashError($e->messageKey());

    return $this->redirect(...);
}

// proceed with the reset, once a new password will be set/saved, this URL will become invalid

final class VerifyToken
{
    public function __construct(private User $user) {}

    public function __toString(): string
    {
        return $this->user->isVerified() ? 'verified' : 'unverified';
    }
}

/** @var \Zenstruck\SignedUrl\Generator $generator */
/** @var \Zenstruck\SignedUrl\Verifier $verifier */

// REGISTRATION CONTROLLER ACTION (GENERATE URL)
$url = $generator->build('verify_user', ['id' => $user->getId()])
    ->singleUse(new VerifyToken($user)) // this token's value will be "unverified"
    ->create()
;

// send email to user with $url

// VERIFICATION ACTION (VERIFY URL)
try {
    $verifier->verifyCurrentRequest(new VerifyToken($user)); // this token's value should be "unverified" but if not, it is invalid
} catch (\Zenstruck\SignedUrl\Exception\UrlVerificationFailed $e) {
    $this->flashError($e->messageKey());

    return $this->redirect(...);
}

$user->verify(); // marks the user as verified and invalidates the URL

// save user & login user immediately or redirect to login page

/** @var \Zenstruck\SignedUrl\Generator $generator */
/** @var \Zenstruck\SignedUrl\Verifier $verifier */

// REQUEST EMAIL CHANGE ACTION (GENERATE URL)
$url = $generator->build('reset_password', ['id' => $user->getId(), 'new-email' => $newEmailRequested])
    ->expires('+1 day')
    ->singleUse($user->getEmail()) // the user's current email
    ->create()
;

// send verification email to $newEmailRequested with $url

// EMAIL CHANGE ACTION (VERIFY URL)
try {
    $verifier->verify($request, $user->getEmail()); // the user's current email
} catch (\Zenstruck\SignedUrl\Exception\UrlVerificationFailed $e) {
    $this->flashError($e->messageKey());

    return $this->redirect(...);
}

$user->setEmail($request->query->get('new-email')); // changes the user email and invalidates the URL

// save user