# flywp/1.7.0/includes/MagicLoginToken.php

FlyWP Helper – Page Cache, Page Optimization, Emails for FlyWP Server Control Panel, version 1.7.0. 187 lines.

- Page: https://pluginprobe.com/plugins/flywp/1.7.0/code/includes/MagicLoginToken.php
- Raw: https://pluginprobe.com/plugins/flywp/1.7.0/raw/includes/MagicLoginToken.php
- Modified: 2026-08-18T05:34:38+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/flywp/1.7.0/code/includes/MagicLoginToken.php#L10-L20`.

```php
<?php

namespace FlyWP;

/**
 * Verifier for the signed, single-use tokens the FlyWP control plane mints for magic login.
 *
 * The format is a contract shared with the control plane, so it lives in one place with no
 * WordPress dependency and can be tested on its own.
 *
 *     flywp-ed25519.<b64url(payload_json)>.<b64url(ed25519_signature)>
 *
 * Two rules when changing this: the prefix is part of the signed material, and the signature is
 * verified against the payload bytes as received, never against a re-encoding of the claims.
 *
 * @since 1.7.0
 */
class MagicLoginToken {

    /**
     * Token version. Part of the signed material.
     */
    const VERSION = 'flywp-ed25519';

    /**
     * Longest token accepted, in bytes. Bounds the work done before the signature check.
     */
    const MAX_LENGTH = 4096;

    /**
     * Longest lifetime a token may claim for itself, in seconds.
     */
    const MAX_LIFETIME = 600;

    /**
     * Clock drift tolerated between the control plane and this site, in seconds.
     */
    const DEFAULT_SKEW = 60;

    /**
     * Verify a token and return the claims it carries.
     *
     * @param string $token      Raw token as it arrived in the request.
     * @param string $public_key This site's FLYWP_LOGIN_PUBLIC_KEY (base64).
     * @param int    $now        Current unix timestamp.
     * @param int    $skew       Clock drift to tolerate, in seconds.
     *
     * @return array|null The claims, or null when the token is not acceptable for any reason.
     */
    public static function parse( $token, $public_key, $now, $skew = self::DEFAULT_SKEW ) {
        if ( ! is_string( $token ) || $token === '' || strlen( $token ) > self::MAX_LENGTH ) {
            return null;
        }

        $parts = explode( '.', $token );

        if ( count( $parts ) !== 3 || $parts[0] !== self::VERSION ) {
            return null;
        }

        // Verify before reading any claim.
        if ( ! self::verify( $parts[0] . '.' . $parts[1], $parts[2], $public_key ) ) {
            return null;
        }

        $payload = self::base64url_decode( $parts[1] );
        $claims  = $payload === null ? null : json_decode( $payload, true );

        if ( ! is_array( $claims ) || ! self::has_valid_claims( $claims ) ) {
            return null;
        }

        return self::is_fresh( $claims, (int) $now, (int) $skew ) ? $claims : null;
    }

    /**
     * Whether this site's key vouches for the signature over $signed_material.
     *
     * Lengths are checked before the libsodium call, which throws on a wrong-size key or
     * signature. Keep those checks.
     *
     * @param string $signed_material   Bytes the signature is supposed to cover.
     * @param string $encoded_signature Signature segment, base64url.
     * @param string $public_key        This site's FLYWP_LOGIN_PUBLIC_KEY (base64).
     *
     * @return bool
     */
    private static function verify( $signed_material, $encoded_signature, $public_key ) {
        // A host can be built without sodium; refuse rather than skip the check.
        if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ) {
            return false;
        }

        if ( ! is_string( $public_key ) || $public_key === '' ) {
            return false;
        }

        $signature = self::base64url_decode( $encoded_signature );

        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
        $raw_public_key = base64_decode( $public_key, true );

        if ( $signature === null || strlen( $signature ) !== SODIUM_CRYPTO_SIGN_BYTES ) {
            return false;
        }

        if ( $raw_public_key === false || strlen( $raw_public_key ) !== SODIUM_CRYPTO_SIGN_PUBLICKEYBYTES ) {
            return false;
        }

        return sodium_crypto_sign_verify_detached( $signature, $signed_material, $raw_public_key );
    }

    /**
     * Whether every claim is present and of the right type.
     *
     * @param array $claims Decoded claims.
     *
     * @return bool
     */
    private static function has_valid_claims( array $claims ) {
        foreach ( [ 'sub', 'sid', 'iat', 'exp', 'jti', 'flywp_user_id' ] as $claim ) {
            if ( ! array_key_exists( $claim, $claims ) ) {
                return false;
            }
        }

        if ( ! is_string( $claims['sub'] ) || $claims['sub'] === '' ) {
            return false;
        }

        if ( ! is_int( $claims['sid'] ) || ! is_int( $claims['iat'] ) || ! is_int( $claims['exp'] ) || ! is_int( $claims['flywp_user_id'] ) ) {
            return false;
        }

        return is_string( $claims['jti'] ) && preg_match( '/^[a-f0-9]{32}$/', $claims['jti'] ) === 1;
    }

    /**
     * Whether the token is inside its stated lifetime, and that lifetime is plausible.
     *
     * @param array $claims Decoded claims.
     * @param int   $now    Current unix timestamp.
     * @param int   $skew   Clock drift to tolerate, in seconds.
     *
     * @return bool
     */
    private static function is_fresh( array $claims, $now, $skew ) {
        $lifetime = $claims['exp'] - $claims['iat'];

        if ( $lifetime <= 0 || $lifetime > self::MAX_LIFETIME ) {
            return false;
        }

        if ( $now + $skew < $claims['iat'] ) {
            return false;
        }

        return $now <= ( $claims['exp'] + $skew );
    }

    /**
     * Decode base64url, rejecting anything outside the alphabet.
     *
     * @param string $value Encoded segment.
     *
     * @return string|null Raw bytes, or null when the segment is not base64url.
     */
    private static function base64url_decode( $value ) {
        if ( ! is_string( $value ) || preg_match( '/^[A-Za-z0-9\-_]+$/', $value ) !== 1 ) {
            return null;
        }

        $padded    = strtr( $value, '-_', '+/' );
        $remainder = strlen( $padded ) % 4;

        if ( $remainder !== 0 ) {
            $padded .= str_repeat( '=', 4 - $remainder );
        }

        // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
        $decoded = base64_decode( $padded, true );

        return $decoded === false ? null : $decoded;
    }
}

```
