Support

Need help?

Most answers are already written down. For everything else, Oneop does not publish a response time — there is no SLA instrument behind one — so the fastest thing you can do is send a complete first message.

Search the help centreEmail support@oneop.ioOpen your workspace

Worth including

  • The email you log in with
  • The URL of the screen it happened on
  • What you expected, and what happened instead

Goes somewhere else

Projects

What a client portal should show — and what it should not

By The Oneop team8 min read

TL;DR

A client portal is a disclosure design problem, not a navigation problem. Clients should see status, deliverables, approvals, invoices and their own people. Internal notes, cost and margin, other clients' data and unreleased work must never be reachable, and hiding them in the interface is not the same as never sending them. The boundary belongs in the query, not the front end.

A client logs in to check a delivery date and reads a risk entry that says the project is understaffed because two engineers were moved to a larger account. Every word of it is true, and none of it was meant for them. Nobody published it. It appeared because the portal showed the project’s risk register, and the risk register is where the delivery team writes what it actually thinks.

Client portals fail in this direction far more often than they fail by showing too little. The design question is not what can we surface. It is what must never leave the building, and what enforces that.

Start from deny, publish deliberately

The only defensible default for client-facing disclosure is that nothing is visible until someone marks it visible. Default-visible with exceptions requires whoever adds a new field, a new note type or a new attachment to remember the client is reading. They will not remember.

Default-deny costs a small amount of ongoing work: someone has to publish things. That cost is the feature. The act of publishing is the review, and a portal without a publishing step has no review.

The same principle applies at the record level, not just the project level. A project being visible should not make every document, risk, update and team member on it visible. Each of those is a separate disclosure with a separate default.

What the client should see

SurfaceWhy the client needs itThe boundary
Project status and progressReplaces the weekly “any update?” emailA derived percentage, not the underlying task list
Milestones and datesTells them what to plan aroundPublished dates, not the working schedule
Deliverables and approvalsApproval in the system beats approval in an email threadOnly files marked for release
InvoicesTheir own account, with downloadable documentsWhat they are charged, never what it cost you
MessagesOne thread per project beats scattered emailClient-facing thread, separate from internal discussion
Their own usersThey manage their team; you do not administer their staffScoped to their company only

Notice how many rows resolve to a derived or curated value rather than a raw record. That is the pattern: compute the number the client needs on the server and send that, rather than sending the rows and letting the interface summarise them.

The four things that must never leak

Internal notes and candid assessment. Delivery needs somewhere to write “this client’s approvals are the bottleneck” without diplomacy. If that place is portal-visible by default, the team will either stop writing it or start writing it somewhere ungoverned. Both outcomes are worse than the note.

Cost, rates and margin. The client is entitled to the price. They are not entitled to the cost. Hours at internal rates, budget consumption, blended margin and the rate card that priced their work are all internal, and exposing them converts a conversation about deliverables into a conversation about your business.

Other clients’ data. The obvious one, and the one most often defeated by a subtle path rather than a broken page: a search that queries across the workspace, a file store that keys on a guessable identifier, a reference in a shared document.

Unreleased work. Drafts, work in review, and the plan before delivery has committed to it. A client reading a date that was still being validated treats it as a promise, and you inherit a commitment nobody made.

The failure case: filtering in the front end

A team ships a portal by reusing their staff project API and hiding fields in the client interface. The endpoint returns the full project object, including the owner, the internal identifier, the deal it came from, and the budget. The portal page renders four of those fields.

Nothing is visible on screen. Everything is in the response. The first time a client’s developer opens the network tab, or an integration is built against the portal, or the page errors and dumps its state, the whole object is readable. No attack was necessary, because the data was already sent.

There is a variant that is worse because it looks deliberate. The team adds a permission check and returns 403 for projects the client cannot see. A client who increments an identifier now learns exactly which identifiers exist, which is a map of your client base by volume and sequence. The check was real; the response leaked anyway.

The lesson in both cases: the boundary has to be in the query that selects the data and in the shape of the response that leaves the server. An interface that renders four fields out of twelve has not enforced anything.

Where the boundary actually belongs

Three mechanisms, in order of how much they carry.

A separate session principal. The portal should have its own credential, its own cookie and its own signing secret. Sharing a signing secret between the staff app and the portal means a token minted in one realm can be replayed in the other, which turns an authentication boundary into a naming convention.

One visibility choke point. Every portal read should derive its scope from one function that answers “which records may this principal see”, and no read should resolve a client-supplied identifier directly. One choke point can be reviewed. Twenty scattered permission checks cannot, and the twenty-first will be written by someone in a hurry.

Whitelist response shapes. The server should never serialise a database row to a portal client. Each entity should leave through an explicit mapper that names every exposed field by hand, so a column added next year is invisible by default rather than published by accident.

How Oneop’s client portal draws the line

Oneop’s client portal is a separate security principal rather than the staff application with navigation removed. It has its own session cookie and its own signing secret, and in production the server refuses to boot if that secret is unset or if it matches the staff session secret or the operator console secret. Login, invite acceptance and password reset are each independently rate-limited.

Every portal read routes through a single visibility function: the set of projects this portal user may see is projects in their company that are marked client-visible, minus any explicit deny, plus any explicit grant. No portal method resolves a client-supplied project identifier directly. A downstream record is returned only when its project is in that visible set, so another client’s project is structurally unreachable rather than checked-and-refused. Out-of-scope records return 404 with an identical body shape to a record that does not exist, which removes the enumeration oracle.

Visibility is per record, not per project. Risks, project updates and team members each carry their own client-visible flag, and the portal queries filter on it. Documents default to not visible, so a file uploaded to a project is internal until someone publishes it.

Responses go through whitelist mappers. Time entries reach the portal as pre-aggregated hours; the underlying minutes, rates, descriptions, task links and approver identities are never mapped. Budgets and billable-rate tables are not mapped at all. Project owner, creator, the originating deal identifier and internal row identifiers are excluded by construction. Progress percentages are computed server-side so no raw cost or rate row is exposed to derive them.

The portal’s data surface is read-only. The narrow write paths clients do have are deliberate ones: approving deliverables, messaging your team, raising support requests and administering their own portal users.

Two facts worth stating plainly rather than burying. The portal is available on every plan including the free one, with a per-plan cap on portal logins and a custom portal domain from Pro. And portal payments run through Stripe Connect, which is implemented but not configured in the shipped build, so it needs a setup step and we will not describe it as live. The security page and pricing carry the detail.

The test to run on your own portal

Log in as a client, open the network tab, and read the raw responses rather than the rendered page. Then take a record identifier that belongs to a different client and request it directly. If the rendered page is clean but the response is not, or if a forbidden answer differs from a missing one, the boundary is in the wrong layer.

What a client sees at the start of a project is set by how the project was created, which is the subject of turning a closed deal into a delivery project. The broader point that a shared record and a shared permission model are different decisions runs through why your help desk and CRM should share the same customer record and why CRM and inventory need the same order context. For agencies, the portal is frequently the entire client-facing relationship, which is exactly why its defaults deserve this much attention.

Frequently asked questions

Should clients see internal task comments?

No, and the safer design is that they cannot see internal tasks at all. Internal discussion contains estimates, disagreements, staffing problems and candid risk assessments, all of which are necessary for delivery and corrosive when read by the client they concern. If you want clients to see progress, publish a status update written for them rather than exposing the thread that produced it.

Should a client portal show project costs or margin?

It should show what the client is being charged, never what the work costs you. Hours, rates, cost lines and budget consumption are internal numbers that explain your margin, and a portal that exposes them turns every invoice into a negotiation about your business rather than about the deliverable. Publish approved billable hours and the invoice; keep the cost side out of the response entirely.

How do you stop a client seeing another client's data?

By scoping every read through one visibility set derived from the logged-in portal user, and by never resolving a client-supplied record identifier directly. If a request names a record, the server should check whether that record belongs to the caller's visible set before it loads anything. Filtering in the interface is not a boundary, because the interface is not where the data is chosen.

Should a portal return 403 or 404 for a record the client cannot see?

404, with the same response shape as a genuinely missing record. A 403 confirms that the record exists, which lets an attacker enumerate your client list by probing identifiers. Returning an identical not-found answer for 'does not exist' and 'exists but is not yours' removes the oracle. This costs nothing and closes a real class of reconnaissance.

Is a client portal just the staff app with navigation hidden?

That is the common implementation and it is the weak one. Hidden navigation means the same session, the same permissions and the same API responses, with some links removed from the page. A client portal worth the name is a separate session principal with its own credentials, its own routes and its own response shapes, so a portal session cannot reach staff data even when the request is crafted by hand.

Written by

The Oneop team

Writing from the team that builds Oneop — an AI customer platform with web chat and email on top of a real CRM, help desk, ledger and warehouse.

Related reading

Start on the free plan and see the real thing

No credit card. Free plan, 2 seats.

Free forever · 2 seats · No credit card