Account & Security — End-User Guide¶
Overview¶
Every application scaffolded on Pramnos Framework ships a complete, self-service account area — no code required beyond branding. This guide is written for the end user of such an application: it explains what each account page does and how to use it.
The account area lives under a single base path (shown here as /account — your
application may brand it differently) and groups everything a signed-in user
needs to manage their identity, security and privacy:
| Area | Pages |
|---|---|
| Profile | View / edit profile and profile picture, dashboard |
| Security | Change password, Two-Factor Authentication, Passkeys, Active Sessions |
| Privacy | Privacy settings, Export my data, Delete account |
All pages share one navigation sidebar and breadcrumb, so the layout is the same whichever theme (plain-CSS, Bootstrap, Tailwind) the application uses.
Profile picture¶
Account → Profile has a Profile Picture card. Choose a JPEG, PNG, GIF or WebP picture.
Where the project has Cropper.js (project:install cropperjs), a dialog opens to crop it to a
square, turn or flip it. The picture is made at most profile_photo_upload_size pixels (512 by
default) in the browser, and sent as soon as it is cropped (see the
Image Upload Guide). Without Cropper.js, press Upload. Whatever the format, it is stored as JPEG, so every client can show it;
a transparent picture gets a white background. The picture shown is a square of 256 pixels
cropped from the centre, and it replaces the previous one. The original is kept beside it,
at most 1024 pixels on its longer side. A file over 10 MB, or one that declares more than 40
million pixels, is refused. A phone photo is turned upright from its EXIF orientation when
PHP has the exif extension. Without the extension it is stored as the camera wrote it,
which can be sideways. Remove takes it away. Until one is uploaded, the application shows its own
default if it has one (the defaultAvatarUrl setting), or no picture.
The same picture appears wherever the application shows the account, with nothing more to do:
GET /mereturns it asavatarurl, an absolute URL;- the OpenID Connect
pictureclaim carries it to the applications you have signed in to; - the administration's user list and every screen that reads the account's
avatarurlshow it.
How it is stored. The picture is kept through the framework's media library. users.photo
holds its media usage, and avatarurl is worked out from that usage when the account loads. A
replaced or removed picture is released, and its files are deleted once nothing else uses
them. The same image uploaded twice is stored once.
Your data. Export my data includes the picture's address under Profile picture. Delete my account deletes the picture with the account.
From a sign-in provider. An application whose users sign in through Google, Microsoft or
another provider can copy the provider's picture the first time, for a user who has none of
their own. It does so only when the profile_photo_from_provider setting is on. The picture
is copied, never linked, because a provider's picture address expires and a page that
allows images only from itself would not show it. The sign-in callback calls:
From Gravatar, when the user asks. The card has a Use my Gravatar button, with a line
saying what it does. Pressing it asks Gravatar for the picture of the account's address and
keeps a copy, converted like an upload. Gravatar is told only about the person who pressed
the button, at that moment, as a SHA-256 of their address. Nobody else's address is sent, and
nothing is sent unless somebody presses it. The server makes the request, so Gravatar sees
neither the visitor's own address nor when their pages are read, and the picture does not
depend on Gravatar afterwards. An address with no Gravatar picture gets "There is no Gravatar
picture for your email address", not Gravatar's generic placeholder. An installation that
wants no contact with Gravatar at all sets profile_photo_gravatar to 0, and the button is
not shown. A privacy policy that lists every third party should still name Gravatar as an
option the user can choose.
Gravatar is never asked for a picture on its own initiative: that would send every address's hash to a third party without anyone asking.
For an application's own code: ProfilePhoto::set($user, $_FILES['photo']) and
ProfilePhoto::fromGravatar($user) return null or the reason they refused,
ProfilePhoto::remove($user) removes the picture, and ProfilePhoto::url($user->photo) gives
the absolute address.
Creating an account¶
If the server has self-service registration switched on, Create Account takes
you to /register. You choose a username and give an email address and a
password of at least 8 characters containing a digit and a symbol.
Many installations do not offer this — accounts are created by an administrator, or by whatever system owns the people using it. On those, the page tells you registration is closed rather than disappearing.
Invited? An invitation mail carries a link that opens the form for your address — it is filled in and cannot be changed, because the invitation is for you. The link works once and expires after two days unless the server says otherwise; ask whoever invited you for a new one.
Asked to confirm your address? Some servers accept only addresses at certain domains, or ask every new account to confirm its address. Then you are mailed a link after registering, and you cannot sign in until you open it. Signing in before that tells you so, and after ten minutes a new link is sent.
Creating an account does not sign you in: you are sent to the sign-in page, so the first sign-in goes through the same checks as every other one.
Signing in¶
The login screen accepts your username or email plus your password. Depending on how the application is configured you may also see:
- "Sign in with a passkey" — passwordless login using a device passkey (see Passkeys below).
- A two-factor prompt after your password, if you have 2FA enabled.
If you enter the wrong password several times, the account is temporarily locked (brute-force protection). Each failed attempt — and any lockout it triggers — is recorded in your activity log.
Staying signed in¶
Ticking "Remember me" keeps you signed in on that device. You can review and end remembered sessions from Active Sessions.
Changing your password¶
Security → Change Password.
Enter your current password, then your new one twice. The new password must:
- be at least 8 characters,
- contain at least one digit, and
- contain at least one special character.
The form checks these rules as you type. On success your password is updated and
a password_changed entry is added to your activity log.
Requiring a change on first sign-in¶
A client application can make users change the password they were given before it lets them in. It needs two things from the server.
When the password last changed. GET /me carries password_changed_at: ISO 8601, or
null if the user has never changed their password. User::getPasswordChangeDate() returns
the same thing as a Unix timestamp. Only a change counts. User::setPassword() dates it
when an account that already has a password is given a different one. A first password
(a new account, or the one an administrator sets on an account that had none) is not
dated, and neither is setting the same password again. Every way the framework writes a
password goes through setPassword(): this page, the reset page, the admin user form and
pramnos user:password. An administrator or operator who sets a different password on an
existing account therefore counts as a change. The date is kept in the passwordchangedate
userdetails field. For a change made before that field was kept, the newest
password_changed entry in the activity log answers instead.
A way back. The change-password page takes returnUrl, and optionally client_id (the
application's apikey), and sends the user there after a successful change:
https://auth.example/Account/changepassword?returnUrl=https%3A%2F%2Fclient.example%2Fwelcome&client_id=<apikey>
The allowed addresses are the ones the applications themselves declare on their edit screen. There is no separate list to keep in step:
- each active application's (
status = 1) URL, - every entry of its callbacks (commas or whitespace between them),
- every CORS origin in its settings.
With a client_id, only that application's addresses count. An address that no application
declares is ignored, and the page returns to the security screen as usual.
Where nothing is declared, any http(s) address is accepted. That applies to the
application a client_id names, or, without one, to every active application. Declaring an
address is what narrows it. A client_id that names no active application accepts nothing;
otherwise a made-up one would unlock every address.
- Scheme and host must match a declared address exactly. The port must match only when
the declared address names one, so a declared
http://localhostadmits any local port. - Only
httpandhttpsare ever accepted. - The address and
client_idsurvive a failed attempt: the error redirect back to the form carries both. They also survive the sign-in detour, because the login page returns to the full URL. - The form page adds the address's origin to its CSP
form-action. Without that, Chrome silently cancels the cross-origin redirect after the POST. - The form page keeps both in the session, so a copy of the view made before it carried
them still returns. The view also receives the address as
$this->returnUrl.
The client's flow is then: read /me; while password_changed_at is null, send the user
to changepassword?returnUrl=<back to the client>; on return, read /me again.
Two-Factor Authentication (2FA)¶
Security → Two-Factor Authentication.
Two-factor authentication adds a one-time code (TOTP) on top of your password.
- Set up: the page shows a QR code — scan it with an authenticator app (Google Authenticator, Authy, 1Password, …) or enter the secret manually.
- Confirm: type the 6-digit code the app shows to verify the pairing.
- Backup codes: you are given one-time backup codes — store them somewhere safe. Each can be used once if you lose access to your authenticator.
Once enabled, you will be asked for a code (or a backup code) after your password on every sign-in. You can regenerate backup codes or disable 2FA from the same page (disabling requires your password).
Passkeys¶
Security → Passkeys.
A passkey is a phishing-resistant credential stored on your device (Face ID / Touch ID / Windows Hello / a hardware security key). It can be used instead of your password or as a second factor.
- Add a passkey: click Add passkey, give it a name (e.g. "MacBook"), and follow your browser/device prompt.
- Rename / remove: manage each passkey from the list; removing one takes effect immediately.
- Sign in: on the login screen choose Sign in with a passkey.
Only passkey metadata (its name, when it was added, last used) is ever stored server-side — never anything that could be used to impersonate you.
Active Sessions¶
Security → Active Sessions.
This lists the devices/browsers currently signed in to your account, with the approximate location (IP), browser and last-seen time. If you see a session you do not recognise, click Sign out next to it — that device is signed out on its next request. Your current session is always kept.
Recent Account Activity¶
The Security page shows your recent account activity — sign-ins, sign-outs, failed sign-ins, password changes, 2FA/passkey changes, application authorizations, privacy changes and data-export requests — each with a timestamp and the IP/browser it came from. Use it to spot anything you did not do.
Authorized Applications¶
Security → Authorized Applications.
If you have granted third-party applications access to your account (via "Sign in with …", or by connecting an assistant), they are listed here. That includes one you approved whose sessions have since expired, because your approval would let it back in without asking. Revoke removes an application's access, invalidates its tokens immediately, and withdraws your approval, so it has to ask again. An application the site has since deactivated can be revoked too.
Privacy settings¶
Privacy.
Toggle privacy preferences such as usage-analytics sharing and marketing emails. Changes are saved immediately and recorded in your activity log.
Each opt-in mailing list the site runs — a newsletter, say — appears here too, unticked until you join it. With a confirmed address, ticking it subscribes you at once; otherwise you are sent a link to confirm, and the screen says it is waiting. Unticking is the same as the unsubscribe link in the mail.
Export my data (GDPR)¶
Privacy → Export my data.
You can download a complete copy of the personal data the application holds about you. The confirmation page lists exactly which sections the export contains (profile, authorized applications, OAuth consents, passkeys, two-factor status, active sessions, tokens, token activity, account details, privacy settings, activity log, mailing-list subscriptions and profile picture — plus any application-specific data).
Confirm to download a single JSON file. For your safety the export never contains secrets — no passwords, token values, 2FA secrets/backup codes, passkey keys, or raw request bodies.
Delete my account (GDPR)¶
Privacy → Delete account.
This permanently erases your personal data. To prevent accidents you must:
- re-enter your password, and
- type
DELETEin the confirmation field.
Deletion removes your data across all account tables. This cannot be undone — export your data first if you want a copy.
If a screen renders blank¶
View::display() returns the markup — it does not echo it. So an action that
renders must either return that value or add it to the document:
// An action declared to return
return $view->display('profile');
// An action declared `: void`
\Pramnos\Framework\Factory::getDocument()->addContent(
(string) $view->display('profile')
);
Calling $view->display(...) as a bare statement from a void action throws the
markup away. The response is still a 200, with the theme, the layout and the
correct <title> — everything except the page.
Related guides¶
- Authentication & User Management — how the login/2FA/passkey stack works under the hood.
- Security Hardening — CSRF, session cookies, escaping.
- Third-Party Integration Guide — for developers connecting an external application.