Download the PHP package zoreal/oauth2 without Composer

On this page you can find all versions of the php package zoreal/oauth2. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package oauth2

zoreal/oauth2

Packagist Version PHP Version CI OpenSSF Scorecard

Login with ZOREAL for PHP backends: the relying-party half of the flow that @zoreal/oauth2-react starts in the browser.

The browser SDK runs the pairing (QR or app link), and hands your frontend an authorization code plus the code_verifier and nonce it generated. Your frontend posts all three to your backend, and this package does the rest: the code exchange with your client authentication, ES256 verification of the ID token against the provider's JWKS, and the /userinfo read for personal claims.

Install

PHP >= 8.1 with ext-curl, ext-json and ext-openssl. One dependency: firebase/php-jwt.

Getting your credentials

Everything the Client constructor needs comes from a ZOREAL asset.

  1. Create an account at https://zoreal.com and open Assets.
  2. Create an asset -- a website (a domain you own) or an app bundle (a reverse-DNS bundle id). An asset is the thing users log in to; its token is your client_id and it looks like ast_....
  3. On the asset, open the OAuth2 tab and register:
    • the redirect URIs and JavaScript origins your app uses (requests from anything not registered are rejected -- this is the core control),
    • the scopes the client is allowed to request (see the catalogue below); a request for a scope you did not register is refused,
    • your client authentication: generate a client secret for client_secret_basic, or register a JWKS for private_key_jwt. A public client authenticates with PKCE alone and no secret.
  4. A website asset must verify its domain (a DNS or meta-tag proof, shown in the dashboard) before it can request personal-data scopes or sign users in; the verified domain is what your users' pairwise sub is derived against.

The client_id is public -- it ships in your frontend. The client secret is a server-side secret: keep it in your secret store and never put it in the browser.

There is no test-identity sandbox -- and that is deliberate

ZOREAL never issues fake or sandbox humans: a pool of test identities would be a fraud vector against the exact thing the product proves. So you always authenticate real ZOREAL IDs.

To develop and test, create a free ZOREAL ID for yourself -- enrol in the ZOREAL ID app and sign in with it. While building, mark your asset's environment sandbox in the dashboard: a sandbox asset may register http://localhost origins and redirect URIs that a production asset may not. Flip it to production when you ship. The identities are real either way; only the allowed origins differ. There is no mock provider and no hosted test issuer to point at.

Quick start

Build one client at boot and share it; it is immutable.

The endpoint your frontend posts to:

Account matching, the shape that works:

Assurance levels -- acr, and requiring a liveness check

What acr is

acr is an OpenID Connect standard claim -- Authentication Context Class Reference. It is a single string in the ID token that says how strongly this particular login was authenticated. Every ZOREAL login carries one, and it is the difference between "someone who once enrolled this identity is behind this request" and "a live human, verified to be the right one, is behind this request right now".

It answers a question sub cannot. sub() tells you who -- a stable, pairwise identifier for this person at your site. acr() tells you how sure ZOREAL is that the person is really there for this login. A stolen, unlocked phone can still produce a sub; it cannot produce a fresh zoreal.live.

The three levels

Ordered weakest to strongest. Each is what actually happened, never what was requested -- a login that could only reach a weaker level says so honestly rather than claiming the level you asked for.

acr What the holder did amr What it proves What it does not prove
zoreal.session Nothing -- a returning holder at a site they have used before, resumed silently from an existing ZOREAL session, no phone interaction [] Continuity: the same session ZOREAL already knew That the holder is present, or even awake
zoreal.device Approved the login on their enrolled phone: a signature from a key in the phone's secure element, released by a local biometric or passcode unlock ["hwk","user"] Possession of the enrolled device and a local unlock on it That a live face was captured for this login -- an unlocked phone in the wrong hands still signs
zoreal.live All of the above plus a fresh face capture this login: a flash-plus-zoom video scored for presentation attacks and screen replay (moire), matched 1:1 against the government document read at enrolment ["hwk","face","user"] A live, real, unique human, verified to be the enrolled person, at the moment of this login -- (this is the strongest level)

amr() (Authentication Methods References) is the companion claim listing the factors used: hwk a hardware key, user a user-presence/unlock gesture, face a face biometric. zoreal.live is exactly zoreal.device with face added, because a live login is a device approval with a capture on top.

The default is zoreal.device, never zoreal.session: a login that asks for nothing still requires the enrolled phone and a local unlock. Silence has to be explicitly asked for (the SDK's prompt: 'none' path), and it succeeds only for a returning holder at a site whose consent they have already given.

When to require which

Requesting versus verifying -- the one rule that matters

Requesting a level and verifying it are two separate steps, and only the second is security:

  1. Request it on the wire, in the frontend, with the SDK's acr_values: 'zoreal.live'. This is what makes the holder's ZOREAL ID app run the face capture before it will approve. It is advisory -- it shapes what the holder is asked to do, nothing more. A browser is attacker-controlled; a value that only travels through it proves nothing.
  2. Verify it here, at token exchange, by passing acr. The signed acr claim in the ID token -- minted by ZOREAL, not by the browser -- is the proof.

An RP that requests zoreal.live on the wire but never passes acr here has checked nothing -- it has only asked the holder nicely and then trusted a value it never validated.

How the check behaves

Verification satisfies upward: zoreal.session < zoreal.device < zoreal.live, so a requirement of zoreal.device accepts a zoreal.live token (the holder gave you more assurance than you demanded). A token whose acr is below the requirement, missing entirely, or outside the vocabulary is refused with VerificationError. An unknown required value -- a typo like 'zoreal.liveness' -- throws ConfigurationError instead, because that is a bug in your code, not a bad token, and failing every login silently is worse than saying so.

If you prefer to branch rather than throw, omit acr and inspect the result:

satisfiesAcr() runs the same ordering as the enforcing check, so it agrees with what acr: 'zoreal.live' would have accepted; a value outside the vocabulary satisfies nothing.

acr versus the assurance block

Do not confuse acr with assurance(). acr grades this login event. The assurance block ($login->assurance()) describes the identity behind it -- how the person was verified at enrolment: the uniqueness basis, the verified_on month, whether chip liveness was proven, the trust_tier, and the device's key_protection. One is about now; the other is about who they are. A high-value flow usually wants both: acr: 'zoreal.live' for presence, and the assurance block for the strength of the underlying identity proofing.

What each call does

Call What happens
authenticate($code, $codeVerifier, $nonce = null, $acr = null) exchange + verifyIdToken, returns a Login
exchange($code, $codeVerifier) POST {issuer}/token with your registered client authentication
verifyIdToken($jwt, $nonce = null, $acr = null) ES256 against {issuer}/jwks, checks iss, aud, exp, nonce when given, and the acr floor when required
userinfo($accessToken) GET {issuer}/userinfo with the Bearer token
Login::userinfo() the above, once, memoized; [] when there is no access token

Login reads the ID token claims directly -- sub(), acr(), live(), satisfiesAcr('zoreal.device'), amr(), assurance(), ageOver(18), nationality() -- and the /userinfo claims lazily: email(), emailVerified(), name(), givenName(), familyName(), birthdate(), documentType(), documentNumber(), issuingCountry(), documentExpiresOn(), portrait().

Client authentication

Your client registers one token_endpoint_auth_method in the dashboard; build the matching ClientAuth here. The provider authenticates against what was registered, not against what arrives.

Scopes and claims

Scopes are requested in the frontend (the SDK's scope string, always starting with openid), consented to by the holder, and pre-authorized on your asset. What each grants and where it is delivered:

Scope Claims Delivered in Tier Requires
openid sub, iss, aud, exp, iat, nonce, auth_time, acr, amr, and the assurance block ID token A any client
zoreal.age age_over_13/16/18/21/65 booleans -- only the thresholds you registered, never an age or birthdate ID token A any client
zoreal.nationality nationality (ISO 3166-1 alpha-3) ID token A any client
email email, email_verified /userinfo B confidential client + verified domain
profile.name name, given_name, family_name /userinfo B confidential client + verified domain
profile.birthdate birthdate (full ISO 8601 date) /userinfo B confidential client + verified domain
profile.document document_type, document_number, issuing_country, document_expires_on /userinfo B confidential client + verified domain
profile.portrait portrait (the chip's facial image; GDPR Article 9 data) /userinfo C confidential client + verified domain -- registrable but not served yet

Error reference

exchange() / authenticate() throw ExchangeError, which carries the provider's own $oauthError code, $description and HTTP $status verbatim. What you will actually see at /token:

$oauthError Cause Retryable?
invalid_grant The code is spent -- unknown, expired (60s), already used, PKCE mismatch, or the asset's domain verification lapsed mid-flow No. Start a new login; the code cannot be reused
invalid_request Client authentication failed -- wrong secret, a bad private_key_jwt assertion, or tls_client_auth (not accepted at /token yet) No. Fix your client configuration
unsupported_grant_type Something other than authorization_code reached /token No. A bug

A failure to reach the endpoint at all (DNS, TLS, timeout) surfaces the same way: an ExchangeError whose $oauthError is server_error. That one is transient -- a retry is reasonable where the three above are not.

Errors that surface in the frontend instead, before your backend is involved (from the SDK's onError / onNonOAuthError), so handle them there:

Where Code Meaning
/pair invalid_scope A scope not on the asset's allowed list, or a Tier B scope from a public client
/pair invalid_request Missing PKCE/nonce, an unverified sector, an unregistered redirect_uri, or an unknown acr_values
/pair login_required prompt=none with no silent session to resume -- the expected quiet outcome, not a failure
pairing request_denied The holder declined in their ZOREAL ID app -- not an error to alarm on; offer to try again
pairing request_expired The pairing window elapsed, or a required liveness the device could not meet -- offer to try again

request_denied is a person choosing not to sign in, not a fault. Treat it like a cancelled dialog: clear any spinner and offer the button again, do not log it as a failure or show a red error.

This package's own exception classes, all extending OAuth2Error so one catch can take the whole family:

Class Means
ConfigurationError You built the Client/ClientAuth wrong, or asked to verify an acr outside the vocabulary -- a bug in your code, not a bad token
ExchangeError The provider refused the code exchange; carries $oauthError, $description, $status
VerificationError The ID token did not verify -- signature, iss, aud, exp, nonce, a non-ES256 algorithm, or the acr floor. A JWKS that could not be fetched or parsed is this too, because it leaves the token unverifiable
UserinfoError The /userinfo call failed. A returning user matched on sub can survive a caught one; a signup that needs the email cannot

OAuth2Error is the shared base; a transport failure (TransportError, internal) is caught by the Client and rethrown as the domain error of the call in flight, so you only ever handle the four above. No error message in this package ever carries a token, a secret or a key.

The assurance block

$login->assurance() is the ID token's zoreal claim -- an array describing the strength of the identity behind this login (distinct from acr, which grades the login event), or null when the token carries none. Its keys and their value sets:

Key Values Meaning
uniqueness personal_number | document | none The anchor the holder is deduplicated on. personal_number (a national number from the chip) is strongest; none means no reliable anchor
verified_on "YYYY-MM" The month the underlying document was verified. Quantised to a month on purpose -- a day-precision date is a cross-site correlator
chip_liveness_proven true | false Whether the passport chip's active-authentication challenge was proven (a genuine chip, not a clone)
trust_tier high | standard high when chip_liveness_proven, else standard
key_protection secure_enclave | strongbox | tee | software How the holder's device key is protected. software means no hardware attestation

A high-value flow usually pairs acr: 'zoreal.live' (fresh presence) with a check on the assurance block (identity strength) -- e.g. requiring uniqueness === 'personal_number' and trust_tier === 'high':

A complete example

A plain PHP login handler, end to end -- the shape a real integration takes. Adapt the $users calls to your own persistence; the ZOREAL parts are exact.

Things worth knowing before you integrate

The ZOREAL OAuth2 library family

Repository Package Role
zoreal-oauth2-react @zoreal/oauth2-react (npm) React frontend: the button, the QR, the polling
zoreal-oauth2-js @zoreal/oauth2-js (npm) Framework-free browser core
zoreal-oauth2-react-native @zoreal/oauth2-react-native (npm) React Native frontend
zoreal-oauth2-node @zoreal/oauth2-node (npm) Node.js backend
zoreal-oauth2-ruby zoreal-oauth2 (RubyGems) Ruby backend
zoreal-oauth2-python zoreal-oauth2 (PyPI) Python backend
zoreal-oauth2-php zoreal/oauth2 (Packagist) PHP backend
zoreal-oauth2-go github.com/Bynn-Intelligence/zoreal-oauth2-go Go backend
zoreal-oauth2-java com.zoreal:oauth2 (Maven Central) JVM backend
zoreal-oauth2-dotnet Zoreal.OAuth2 (NuGet) .NET backend
zoreal-oauth2-rust zoreal-oauth2 (crates.io) Rust backend

License

MIT.


All versions of oauth2 with dependencies

PHP Build Version
Package Version
Requires php Version >=8.1
ext-curl Version *
ext-json Version *
ext-openssl Version *
firebase/php-jwt Version ^7.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package zoreal/oauth2 contains the following files

Loading the files please wait ...