RFC-149: Forge User Impersonation (FUI) - Addressing Connect Parity Gaps & Edge Cases

RFCs are a way for Atlassian to share what we’re working on with our valued developer community.

It’s a document for building shared understanding of a topic. It expresses a technical solution, but can also communicate how it should be built or even document standards. The most important aspect of an RFC is that a written specification facilitates feedback and drives consensus. It is not a tool for approving or committing to ideas, but more so a collaborative practice to shape an idea and to find serious flaws early.

Please respect our community guidelines: keep it welcoming and safe by commenting on the idea not the people (especially the author); keep it tidy by keeping on topic; empower the community by keeping comments constructive. Thanks!

Project Summary

Atlassian is addressing Forge User Impersonation gaps to achieve functional parity with Connect and remove blockers for partners migrating their apps to the Forge platform, specifically regarding unverified JSM customers, Confluence guests, and service accounts. This RFC discusses known current parity gaps, and asks for feedback on other gaps that are blocking migrations from Connect to Forge.

  • Publish: Sep 30, 2026
  • Discuss: Oct 14, 2026
  • Resolve: Oct 28, 2026

Problem

With the rollout of Forge User Impersonation (FUI) across Jira, Confluence and Jira Service Management, apps can now execute API calls on behalf of end users (asUser() and Forge Remote user tokens), enforcing user permissions and attributing change history directly to human users.

While the core in-session impersonation flow works for active, verified users across Jira and Confluence, partner feedback and platform testing have highlighted offline (ie, impersonating a user from a scheduled trigger), and account-state edge cases that functioned in Connect but fail or behave differently in Forge.

We are sharing our technical gap analysis, our proposed roadmap, and opening a discussion on how we should address these edge cases. Our goal with this RFC is to gather more use cases around what’s blocking our partners from migrating from Connect to Forge.

Note on Pre-Migration Scenarios:
This RFC specifically addresses runtime Forge platform parity and app behaviour. Pre-migration fixes and migration-specific tooling edge cases (e.g., user mapping/account states during app data migrations) are managed separately. For assistance with migration use cases, partners should reach out directly to Cloud Migration SMEs.

Current Parity Analysis: What works vs Edge Cases Identified

Please note, that just because a scenario worked in Connect doesn’t mean it will work in Forge. The security model for Forge is different from Connect, meaning that functionality between two platforms will not be identical.

Based on our testing across Forge functions and Forge remote, here is our current assessment of user impersonation parity.

Our team is actively working on this, we plan to update this matrix in the following days as we improve our understanding.

  • Licensed User:
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): :white_check_mark:
    • Forge impersonation call (asUser(<userId>) / Remote): :white_check_mark:
  • Confluence Guest:
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): :cross_mark:
    • Forge impersonation call (asUser(<userId>) / Remote): :white_check_mark:
  • Jira guest
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): :white_check_mark:
    • Forge impersonation call (asUser(<userId>) / Remote): :white_check_mark:
  • Unlicensed org member
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): :cross_mark:
    • Forge impersonation call (asUser(<userId>) / Remote): :cross_mark:
  • Unverified JSM External Customer Account (prefixed with qm:)
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): :cross_mark:
    • Forge impersonation call (asUser(<userId>) / Remote): :cross_mark:
  • Unlicensed JSM Portal users:
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): :cross_mark:
    • Forge impersonation call (asUser(<userId>) / Remote): :cross_mark:
  • Service account
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): N/A (can’t log in as a service account)
    • Forge impersonation call (asUser(<userId>) / Remote): :cross_mark:
  • Unverified Atlassian accounts
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): :cross_mark:
    • Forge impersonation call (asUser(<userId>) / Remote): :cross_mark:

Proposed Scope

Unverified JSM External Customer Accounts

  • In JSM, email is a primary channel. When an external customer emails a service desk, apps create or resolve customer accounts and create tickets or comments under that customer’s identity.
  • Portal customers may never visit the customer portal or verify their account. Requiring they login before an app can act on their behalf breaks the core email-driven service desk model.
  • JSM Customer accounts can be created programatically via the API (for example). Since these accounts are synthetic, they are not verified at the time they need to be used.

Confluence Guest Users

  • External collaboration relies on guest accounts and anonymous viewers being able to access designated spaces. When a guest interacts with a Forge app, asUser API calls initiated on their behalf fail.
  • From our testing, this diverges from how Jira guests work currently. They can make calls themselves, or be impersonated.

Service Accounts

  • Service accounts are accounts that are not associated with a person. They can currently be impersonated in Connect but not Forge.
  • Depending on the scenario, they can be useful as a workaround for being unable to impersonate unverified users in Forge.

Unlicensed users (eg Give access to unlicensed users from Jira Service Management | Confluence Cloud | Atlassian Support )

  • Some apps rely on impersonating unlicensed users, for example, in a JSM portal that is open to the public or to other members of your organisation. This currently works in Connect but not Forge.

Out of scope

The following Forge User Impersonation bugs are out of scope in the context of Connect to Forge parity gaps. We hear you that these are problems, and they’ll be addressed separately.

  • Password Reset bug described in ECO-1467
  • asUser API request for verified JSM customer accounts described in FRGE-971

Asks

We’ll enable impersonation for unverified JSM external customers, but enabling impersonation for standard unverified Atlassian accounts is still under investigation.

Atlassian accounts differ fundamentally from portal-only customers:

  1. Broader access and blast radius: A standard Atlassian account holds permissions across products and orgs.

  2. Account ownership and identity verification: Impersonating unverified email addresses have trust considerations that we must evaluate (e.g., identity spoofing) and unauthorised privilege escalation risks if the identity cannot be verified

What we need to understand from you

We know partners rely on Connect’s impersonation functionality to act as unverified Atlassian users. However, we need to understand the exact use case to address open concerns on our end.

  1. Would improved error messaging solve the problem? If Forge returned a distinct, actionable error code allowing your app UI to prompt an admin or user to complete verification/login, would that satisfy your customer scenarios?
  2. If not, why? Please explain explicitly why an interactive prompt is not viable for your architecture. Any additional information such as user stories, web sequence diagrams, whiteboarding etc is more than welcome and our team will find this very helpful. If you prefer to discuss specifics with our team privately, please let us know and we can coordinate accordingly.

We want to ensure no critical edge cases slip through the cracks so please share if:

  1. your app has other unverified scenarios that block you from migrating to Forge
  2. there are any scenarios that have not been covered in this RFC that worked in Connect but fail in Forge today

Hi Scott,

thanks for taking this up. I have a few questions, you mention “Unverified JSM External Customer Account” vs “Unlicensed JSM Portal users” - not sure what the latter is, but would this be in scope as well?

We are using the JSM create-customer API and then directly afterwards impersonate the created user. I’m wondering if this will always create an “Unverified JSM External Customer Account” or is there a case, let’s say if you have a managed domain in Access, where the create customer API call will actually create an “Unverified Atlassian account”? Due to the domain of the created user being managed by Atlassian access?

Thanks

Tobias

Hi @ScottGangemi, thanks for publishing this RFC and for sharing the parity matrix.

We’d like to ask for clarification of the terminology used in the matrix, because we’re struggling to map our real-world scenario to a single row.

Our scenario

Our app (Forge, JSM customer portal context) makes asUser() calls to the JSM/Jira REST API on behalf of the user currently interacting with the portal. We see a lot of failures for users who:

  • have a regular Atlassian account (not a portal-only qm: account),
  • have the Customer role for Jira Service Management on the site (no Agent license),
  • are members of the customer’s organization,
  • are logged in and actively using the portal, so this is an online asUser() call, not offline impersonation.

Typical failures: GET /rest/servicedeskapi/…’ returning 401 error code. Details are in our internal ticket ECOHELP-163246.

Could you clarify the terms?

Could you explain the terms used in the matrix, ideally in relation to the account types already defined in Access to Forge apps for unlicensed users (licensed, unlicensed, customer, anonymous)? These are the values developers actually see as accountType. It would also clear up an apparent contradiction: the docs say asUser() is allowed for JSM customer users, while the RFC has no customer row and marks “Unlicensed JSM Portal users” as unsupported.

BTW, linking to a comment in FRGE-971 as the definition of what’s out of scope only adds confusion, so please describe it directly in the RFC.

Proposed matrix

We’d suggest describing each row with the following parameters:

  • account type (as defined in the Forge docs),
  • identity type (Atlassian account vs. portal-only qm: account, Service Account ),
  • verification status,
  • product role (JSM Agent, JSM Customer, no product access),
  • impersonation mode (online vs. offline),
  • call type (asUser() vs. asUser(<userId>) / Forge Remote).

Example of how the matrix could look:

account type identity type Verified Product role Online (asUser()) Offline (asUser(<userId>) / Remote) Notes
licensed Atlassian account Yes JSM Agent :white_check_mark: :white_check_mark:
unlicensed Atlassian account Yes JSM Customer :cross_mark: ? Our case, In scope of this RFC?
customer Portal-only (qm:) Yes - ? ?
customer Portal-only (qm:) No - :cross_mark: :cross_mark: In scope of this RFC
unlicensed Atlassian account Yes No JSM access ? ? e.g. org member licensed only for Jira
unlicensed Atlassian account No No JSM access :cross_mark: :cross_mark: Under investigation
unlicensed Atlassian account Yes Confluence guest :cross_mark: :white_check_mark: In scope of this RFC
anonymous – – – N/A N/A

A matrix like this would make it immediately clear which scenario is supported, which is planned, and which is out of scope.

Error messaging

A “verify / log in” prompt wouldn’t help in our case, since the user is already logged in. A distinct error code would still help to tell permission errors apart from impersonation limitations.

Thanks!

Hi @ScottGangemi,

The principal ask here seems to be “Would improved error messaging solve the problem”. I struggle to see how this question would apply to at least the following case:

  • Confluence Guest:
    • Connect user impersonation: :white_check_mark:
    • Forge online calls (asUser()): :cross_mark:
    • Forge impersonation call (asUser(<userId>) / Remote): :white_check_mark:

Forge user impersonation works in at least two of these scenarios (asUser with a specific userId, and from Forge Remote), which implies that Atlassian does trust the underlying user to at least some extent.

An improved error message does not help here. Can Atlassian perhaps fix the underlying limitation and allow this to work as expected in the general asUser() case?

As an aside, I find the whole trajectory of Forge user impersonation features to be perplexing. I recognize that the internal security model is different from Connect and that there are various internal reasons as to why Atlassian did not want to implement certain features on Forge.

That having been written, all of these underlying internal implementation issues were hidden from vendors in the Connect implementation. At an application level, the accountId is an opaque object and we have (mostly) no way to differentiate one account type from another, so having certain impersonation requests randomly blow up is a bad developer experience.

This feels like a large game of whack-a-mole, with one edge case after another needing to be negotiated separately by vendors.

If Atlassian’s concern is that apps could inadvertently take actions on behalf of unverified users, have you considered some sort of universal solution (avoiding having to deal with each individual case) that could allow apps to explicitly own the “we know this user is unverified but we want to impersonate them anyway” behaviour, if they need it? And allow this for all types of users, period?

For example, for any user account that is unverified, you could require the app to include a HTTP header conveying ‘we know that this user is unverified and we want to impersonate anyway’, or else refuse the request with a specific HTTP response (letting the app know that it needs to retry with the special header if it is OK operating on an unverified account).

You could even refuse requests with this header when it is applied to requests for verified accounts (to prevent app vendors from blindly applying this header to all requests). All of this allows apps to own the fact that they are operating with a potentially-unverified account and to govern their actions accordingly.

It is admittedly unclear as to exactly which security problem Atlassian is trying to solve by limiting all of these types of impersonation in the first place, but I hope that something like the above might be sufficient. Vendors generally do not want to have to deal with a half-dozen different edge cases of why a previously-working solution might break (and to have to explain to customers why certain accounts work and others do not).

Hey Tobias,

An unverified account is someone who hasn’t logged into their account, and unlicensed means that they have an account in your org and don’t have access to a particular product. This may mean they have no granted product access, or may have access to Confluence, but not Jira (for example). External in the context of JSM means that the customer has a portal-only account, and isn’t visible at an org level in Atlassian Administration.

For the JSM create customer API, if you impersonate the created user straight away they’ll be classified as unverified and external, so your use case will be covered as part of this project. From my understanding of the code, users created in this API aren’t checked against owned domains so will remain external unless they’re migrated to an Atlassian account in the future.

Thanks!

Hey Peter,

I can see that that ticket is being investigated by our support process, so I’ll let that run it’s course.

Thanks for the feedback on the table, and on having a clearer error code/failure state!

Reading this as someone on both sides of it: I build Forge apps, and I administer Jira and Confluence sites where other people’s Forge apps run. The parity cases are right, and the four vendor replies above cover the developer side well. What is missing from the RFC is the admin side, and I would like it in scope before resolution.

1. Attribution in the audit log. Today an asUser() action lands in history as the user, and the admin can usually reason “that person was logged in and did that.” For the four classes in this RFC that reasoning breaks: an unverified customer has never logged in, a service account has no human behind it, and a guest or unlicensed user may not know the app exists. Please state in the RFC what the org audit log and the work item history will record when an app acts as one of these accounts. The minimum I would want is the acting app’s identity alongside the impersonated principal, the way the Rovo MCP audit filter now records tool, action and user per call. Without that, a security review cannot distinguish “the customer replied” from “an app replied as the customer.”

2. Service accounts need an admin-side gate, not just a developer-side one. @Scott Dudley’s explicit header is a good control for the developer. It does not help the admin, who cannot see headers. For service accounts specifically, I would want impersonation to be an install-time or scope-time declaration that surfaces in the app’s permissions review, so the admin approving the app sees “this app can act as service accounts” before it does. Service accounts are usually the identities with the broadest permissions on a site; silently extending impersonation to them changes the blast radius of every installed app that already had the scope.

3. One question the RFC should answer: do Data security policies and the app-level controls admins already have apply when the actor is an impersonated service account or unverified customer? If an admin has restricted an app to certain spaces or classifications, the answer needs to be “yes, exactly as for a logged-in user,” and it needs to be written down.

On Atlassian’s first question: better error messaging does not solve the admin cases above. It solves the developer’s debugging problem, which is worth doing, but the governance gap is about visibility after the call succeeds, not clarity when it fails.

I support the parity work. I am asking that the audit and consent model ship with it rather than after it.

Thanks for this RFC. Here is a concrete, reproducible case for the unverified JSM external customer row, from a production Jira Service Management site.

Use case

  • We run our own customer portal in front of JSM.
  • Customers are onboarded by our admins: we create the JSM organization and add or invite the customers, who are portal-only customer accounts with no Jira access and no licence.
  • Customers must be able to reply to and resolve their own requests from our portal and be shown as the author, exactly as if they had used the JSM portal or email.
  • Asking every customer to grant consent individually isn’t acceptable for us, and it isn’t needed: the app uses Sharing distribution, so the admin consent covers it.

Setup

  • Forge app with Sharing distribution, installed by a site admin.
  • Forge Remote with endpoint + auth.appSystemToken and an hourly scheduledTrigger. The system token arrives at the remote as expected.
  • Scopes with allowImpersonation: true: classic read:servicedesk-request and write:servicedesk-request, plus the granular JSM request/comment/status scopes.

Observed

Principal offlineUserAuthToken mint GET /rest/servicedeskapi/request/{key} with the minted token In-Forge asUser(accountId).requestJira(...), same call
Licensed app contributor succeeds 200 200
Portal-only customer (qm: accountId, token claim verified=false) succeeds (ttl 14400) 401 {"code":401,"message":"Unauthorized"}, no WWW-Authenticate throws NEEDS_AUTHENTICATION_ERR
  • The customer’s token claims show accountType=customer and verified=false.
  • verified stays false even after the customer set a password through the portal “forgot password” flow, logged in to the portal, viewed the request and commented on it.

Update (same day): verified customers work, so the issue is the qm: accounts

  • A customer invited through the JSM agent view is a full Atlassian account (712020:…) and is verified.
  • Customer mode works fully for that account: reads, public comments, Resolve, Cancel and Escalate. JSM records the customer as the actor on tickets they raised, tickets raised on their behalf, and tickets shared with their organization.
  • Early 403s right after the invite cleared after a short delay.
  • So the remaining gap is exactly the RFC-149 row: unverified qm: portal-only customers, which is how every customer created by email or through the API is set up.

Context: on 2026-10-01 you wrote in this thread: “For the JSM create customer API, if you impersonate the created user straight away they’ll be classified as unverified and external, so your use case will be covered as part of this project.” That is exactly our case. Customers created through POST /rest/servicedeskapi/customer or by email are qm: portal-only accounts. FRGE-971 and ECO-1507 report the same behaviour, and on ECO-1507 an admin-set password plus a login didn’t fix it either.

Questions

  1. When will impersonation of unverified qm: JSM customers ship? Is there an EAP we can join? We can test immediately on a dedicated test desk.
  2. Will the fix cover Forge Remote offline impersonation (offlineUserAuthToken) as well as in-Forge asUser()?
  3. Is a portal module with unlicensedAccess: [customer] required for impersonating customers, or is Sharing distribution plus the scopes enough?
  4. Does an admin-set or reset password on a qm: customer permanently affect impersonation tokens for that account (ECO-1467)?
  5. Can you expose a customer’s verified status through an API, so integrations can decide up front whether to act as the customer or fall back?
  6. Until the fix ships, is there any supported way for an admin to make an admin-onboarded customer (JSM organization plus invite or API create) count as verified, without each customer having to act?

We are also voting on and watching ECO-1507 and FRGE-2233.

Hi @ScottGangemi ,

The initial RFC skipped out the context of both:

These tickets have been ongoing for some time, block Connect EOS migrations and impact vendors ability for AEC compliance due to being unable to utilise Forge impersonation for various reasons (parity, verified limitations, etc).

Would improved error messaging solve the problem?

No. Improved error messaging would only allow us to react to the unverified account being unable to impersonate. This does not solve the parity between Connect actAsUser, vs Forge imprersonation.

If not, why?

An interactive prompt is obsolete when the processes requiring the impersonation are background processes. Our use case, plus the use case for other vendors, relies on the Offline Remote User impersonation feature.

We want to ensure no critical edge cases slip through the cracks

We have on numerous occasions over the past 8+ months engaged with Atlassian regarding what limitations are for Forge impersonation and how our app uses the existing Connect actAsUser. Without parity, the breaking behaviour falls to app vendors, without Atlassian providing clear cut reasoning why impersonation is not the same for Forge.

Our use case, for both Atlassian User accounts and Portal Customer accounts remains the same in Connect. App functionality can create new accounts (User or Customer) from the Forge Remote, performed as a background scheduledTrigger task. Any subsequent APIs or impersonation attempts attempted by this account would fail due to the account not yet being verified.

However, regardless of if an account was created by our app, there may be an account that was created by an Admin, but not yet verified or logged in.

Our app would be unable to impersonate accounts in all above scenarios. The impersonation ranges from varying operations defined in the manifest scope (For issue/request create/update, project context retrieval, entity property and comment property lookup, etc). The actions an account can perform should be the same as what was available in Connect.

I was under the assumptions that after the related ECO/FRGE tickets and Atlassian communication, that there was an understanding from Atlassian on what vendors need in order to use Forge offline impersonation (which is preventing Connect migration). If this is not the case, can you please clarify further?

Request is to add the accountType property in the forge bridge getContext(). Due to some of the limitations as well as business rules we have to branch behavior in the app modules depending on the account type (i.e. atlassian, guest, customer, anonymous), in Jira JSM and Confluence.

The Atlassian apps in the browser knows if it is a guest and the displayConditions know if it is a guest to show/hide modules. Publishing that known info in the context will save a call an unnecessary nested call to the API to get the current user info on every applicable module. For us this will benefit high volume Confluence macros and JSM portals as well as regular app pages.

@scott.dudley, thanks for the feedback. Agreed that it’s feeling whack-a-mole-y and that the internal implementation shouldn’t be leaking out.

@ahmedsamishk, thanks! That’s useful to hear that from your admin perspective.

  1. For the audit logs, would you want this to cover all instances of impersonation? Licensed users, unlicensed users, verified users, unverified users etc?
  2. Noted. We can look at improving the app’s permissions review in the impersonation context generally too.
  3. It is the same as it would be for a logged-in user, but our docs don’t do a good job of showing this. A small part of this project is to audit the documentation and make sure it’s up to scratch and as useful as it can be, I’ll make sure we include this with some examples.

@Jon.F
Thanks for the detailed use case and confirmation.

  1. I don’t have a better answer for you than “soon”, I’m afraid. We’re internally figuring out what implementation looks like on this and then will get started on it. For your context, we’ve got a team that’s working on this project right now so the answer will be in the magnitude of weeks not months. Since FUI is already out there, we won’t have an EAP but will roll this out behind a feature flag. I’ll ping you here once it’s released.
  2. Yes it will.
  3. Sharing distribution plus scops is enough.
  4. Not permanently. ECO-1467 is essentially an internal caching issue (an over-simplification), but it means that eventually that user can be impersonated again.
  5. This already exists here. I assume this isn’t something you’d care about if you could impersonate an unverified user though, is that right?
  6. No there isn’t unfortunately. Appreciate this is blocking you though, we’re working on resolving it.

@DennyMiller, thanks for your reply! ECO-1507 and FRGE-2233 are both on our radar and from my read of them (correct me if I’m wrong please) is that they focus on unverified users and unverified JSM Customers, anything else I missed? Good to know updated errors wouldn’t help you here as well.

You’re right that after the ECO/FRGE tickets we know a large subset of the issues that are preventing vendors from migrating to Forge. One of the purposes of this RFC is to try to flush out any remaining issues that we haven’t heard about. We know that unverified scenarios are a huge use case, but there may have been some vendors who haven’t spoken up on these tickets, or may have some other use case that we didn’t know about.

Thanks for opening this up, Scott. The Confluence-guest and unverified-customer rows have already been well argued in this thread (+1 — they match our experience), so I’ll focus on the one target row that hasn’t had a concrete vendor use case behind it yet: Service account.

Why this row matters to us

On Connect we relied on service-account impersonation to carry out app-driven work under a stable, non-personal identity — an account deliberately not tied to any individual, so actions are attributed consistently and don’t break when a person offboards. That row is :cross_mark: for Forge impersonation (asUser(<userId>) / Remote), and we haven’t found a documented equivalent that preserves this behaviour.

The fair question is “why not just run as the app (asApp() / app system token)?” In some cases that’s a perfectly good answer. It stops being equivalent when the attributed actor matters: acting as the app changes what the audit log and content history record (the app, rather than a named operational identity), and the app principal doesn’t inherit the permissions or content ownership that the service account holds. Where either of those applies, there’s currently no Forge path that matches what Connect did.

On “would improved error messaging solve the problem?”

Clearer, distinct error codes would genuinely help — today this is hard to tell apart from an ordinary permission failure, and better signalling would cut our debugging and support load. But it doesn’t unblock migration: the gap is a missing capability, not missing clarity. A better error still leaves us with no identity to perform the work under.

What we’re asking for

  1. Please treat service-account impersonation as a formal Connect→Forge parity / AEC-compliance blocker rather than an edge case — it worked on Connect, and the absence of an equivalent is part of what’s holding up our migration.
  2. If there is an intended Forge-native way to “act under a stable, non-personal identity” — whether that’s extended asApp() attribution, a first-class service-account token, or something else — it would help a lot to have it stated explicitly in the RFC, with the attribution and permission behaviour documented.

Hi Scott

In our app, we need asUser() to work for Confluence guests too.
No, improved error messaging does not help.

Why not
The guest is already in an active session in Confluence, interacting with our app. They aren’t unverified.

When a guest triggers an action in our app, asUser() fails because there’s no usable accountId for the guest in that context. We already detect the failure and tell the guest they lack permission, but they still can’t complete the action. A distinct error code would help debugging, but it wouldn’t unblock the user.

We can’t work around it safely. Our actions have to be permission-checked as the acting user and attributed to them. Falling back to asApp() would skip the per-user permission check and lose attribution in the audit trail and content history.

We would need asUser() to work, as it does for licensed users, to match the behaviour we had on Connect.

We hit the “Unlicensed users” gap from the RFC’s parity table in our JSM portal app, and we have measurements that may help scope it. Our main case is online, not offline impersonation, and I think it shows the gap is in how the user token is passed along, not in whether the user can be authenticated.

Test: one app, one site, one portal page, two accounts. The first is unlicensed (accountType: unlicensed). The second is a licensed control account (accountType: licensed).

Called from Call Unlicensed Licensed
Browser @forge/bridge requestJira /rest/api/3/myself 200 200
Browser @forge/bridge requestJira /rest/servicedeskapi/request 200 200
Forge resolver asUser() /rest/api/3/myself 401 200
Forge resolver asUser() /rest/servicedeskapi/request 401 200
Forge Remote asUser() /rest/api/3/myself 401 200
Forge Remote asUser() /rest/servicedeskapi/request 401 200

All 401s return Client must be authenticated to access this resource.

For unlicensed callers, Forge Remote never receives x-forge-oauth-user.

What Forge Remote receives Unlicensed Licensed
Forge invocation token present present
principal caller’s own accountId caller’s own accountId
x-forge-oauth-system present present
x-forge-oauth-user absent present

The platform knows who is calling, but for the unlicensed caller it doesn’t send the user token. Without it, asUser() on the remote has no user credential to call Jira with.

Second case: offline impersonation of unverified users. We also have a “view as” feature that lets us preview the configuration as a specific portal user will see it, including before that user has verified their account. This relies on offline impersonation of that user. It worked on Connect, and on Forge it doesn’t work for unverified users, so this is another parity gap for us.

On the RFC’s asks:

  • Would better error messaging help? No. The user is already logged in, and the bridge authenticates them for the same resource. There is nothing the app or the user can do about it.
  • asApp() as a fallback? Doesn’t work for us. On /rest/servicedeskapi/request it returns 200 with an empty list for both accounts, because the app is a participant on no requests.

Questions:

  1. Does the “Unlicensed users” work cover online calls — a user on a portal page triggering invoke() / invokeRemote() — so that asUser() works in the resolver and Forge Remote receives x-forge-oauth-user? Or only offline asUser(<accountId>) impersonation from triggers and async events? The answer to @Jon.F confirmed Remote for his scheduled-trigger case. We need the online path, and offline impersonation of unverified users as well (see above).
  2. The same unlicensed account gets 200 from @forge/bridge requestJira on the same resources, through api.atlassian.com, so the platform can already authenticate this user for this app. Is the missing user token in resolvers and Remote tracked as a defect, separate from the parity project? If so, is there a ticket we can follow?

Hello Scott,

Our app is focused on workflow post functions, scheduled actions, and event-based actions. Impersonation is quite important in our product - we use Forge asUser(accountId) extensively (with allowImpersonation on our Jira/JSM scopes) whenever admins configure “Run as” other than “Addon user”. After migrating to Forge, we currently have 2 use cases related to user impersonation that don’t work well:

  1. Running post-functions as JSM customers - on Connect this worked. On Forge we’re seeing 401s in production when “Run as” ends up as a JSM portal customer. Example setups: run as user from field (reporter, participants, custom user field) or run as user from script on SD requests - agent triggers the transition, but the action should show up as the customer. Same configs also run off the request (queued post-functions, scheduled/event actions) - we still call asUser(accountId) with whatever account ID “Run as” resolved to. This sounds like the the problem you’re already planning to fix, so we are definitely in favor of this. Main thing we would want to confirm is whether the fix isn’t only Forge Remote / offline tokens- in our case it needs to cover normal in-Forge asUser and async consumers too. And answering the question “Would better errors be enough?” - it would be helpful for debugging, but not for our users. Nobody is going to get the customer to log in and verify in the middle of a background workflow.
  2. Service accounts - customers also configure “Run as selected user” (or from a field) where that user is a service account. As far as I remember it worked on Connect, on Forge it doesn’t really have an equivalent. Falling back to “Addon user” isn’t the same thing (different permissions + history shows the app, not the service identity they picked), so we would definitely also like it to work on Forge.

And as an addition, it would be nice if error messages like “401 - AUTH_TYPE_UNAVAILABLE” were clearer.

Apologies for the slow reply, and yes: all instances, with no exception by licence or verification state. The reasoning from the admin chair:

  1. Attribution is about the action, not the actor’s licence. When an auditor asks “who changed this and under what authority”, the answer “an app, acting as an unverified portal customer” is exactly the case that needs a record, because it is the one nobody will remember approving. An audit entry that only appears for licensed, verified users would leave a hole precisely where the risk is highest.
  2. One entry shape, four facts. App (id and version), the impersonated principal (accountId), the principal’s account type and verification state at the time of the call, and whether the call was interactive or offline (Remote). With those four fields, admins can filter the noise themselves; without the type and state fields, the log confirms that impersonation happened but not which of the matrix rows it fell into, and that matrix is the whole point of this RFC.
  3. Service accounts are the row that also needs a gate, not just a log. For user impersonation, a complete audit trail plus the existing scopes is enough for me. For service-account impersonation, I would want the install or scope-time consent to name it explicitly, because that is the one case where an app acts under a durable, non-personal identity with standing permissions (Stanford Kamau’s point above, from the vendor side, is the same concern from mine).
  4. Volume is not a reason to exclude the unlicensed and unverified rows. If a portal with heavy anonymous traffic generates a lot of entries, that is information, not noise; give us a filter in the Guard audit log by event type and app, and let the admin decide.

So: log everything, expose type and state on each entry, gate service accounts at consent time. If the team ships parity for the customer and guest rows first and the audit fields follow, say so in the resolution post so admins can plan; what I want to avoid is parity shipping with the audit gap undocumented.

Thanks, Scott, for putting this RFC together and for addressing the remaining Connect parity gaps.

We’d like to add another confirmation that the Unverified JSM External Customer Account scenario described in this RFC is a critical Connect-to-Forge migration blocker for us.

Our use case is email-driven: when an external customer sends an email to a service desk, our backend may create the JSM customer account if it does not already exist, and then needs to perform the corresponding action in that customer’s identity, such as creating a request or adding a public comment.

These customers may never visit the customer portal or complete the verification flow at all. Email can be their primary or only interaction channel with the service desk, so requiring them to log in or verify their account before their email can be processed would break the existing workflow.

For the same reason, improved error messaging would not solve the problem. This processing happens asynchronously on the backend, without an interactive user session where we could ask the customer to verify their account.

This workflow works with Connect impersonation today. Supporting offline impersonation of unverified JSM external customers in Forge is therefore necessary for us to migrate this functionality without a regression.