Delegated and application access
Which identity each server-side Rayfin surface uses — the signed-in user or the app — and what access to grant at each data source.
Server-side Rayfin code reaches three kinds of data: entities your app owns, data that already lives in Fabric (through connectors), and other Microsoft resources (through function connections). Every one of those calls runs as one of two identities:
- Delegated — the signed-in user. Rayfin starts from the user's Microsoft Entra ID session (Fabric SSO, direct Entra token sign-in, or an embed host) and acts on that user's behalf. The source decides what that user may read and write.
- Application — the app itself. For current Fabric apps, the app identity is the owner of the Fabric app item. Every user of the app gets the same access, so the app identity — not each user — needs permission on the source.
Rayfin 1.36 moved two surfaces to the app identity. Function connections now run only as
the app identity, and rayfin connector add configures SQL connectors to run as the app
identity. In 1.35, both ran as the signed-in user.
Which identity each surface uses
| Surface | You declare | Runs as, once deployed | Who needs access at the source |
|---|---|---|---|
Your own data from the browser — client.data.<Entity> | @role() / @authenticated() policies on the entity | Signed-in user | Nobody — entity policies decide |
Your own data from a function — ctx.getDataClient() | The same entity policies | Signed-in caller | Nobody — entity policies decide |
Function connections — ctx.Tokens.<Audience> | RayfinContext<Schema, AudienceType.X> and services.functions.auth.type: application | App identity | The app identity |
| SQL connectors — warehouse, SQL database, Lakehouse SQL endpoint | auth.type in rayfin.yml; connector add writes application | App identity, or the signed-in user with delegated | The app identity, or each user |
| Semantic model connectors | auth.type: delegated (the only allowed value) | Signed-in user | Each user |
Your own data always runs as the caller
Entities in rayfin/data/ are protected by permission decorators.
A policy callback compares the caller's claims to fields on each row, so
client.data.<Entity> returns only the rows that caller may see.
The same is true inside a function. ctx.getDataClient() uses the invocation's Rayfin token,
so entity policies apply to the user who invoked the function. Setting
services.functions.auth.type: application does not switch your Rayfin data to the app
identity.
Function connections run as the app
Since Rayfin 1.36, enabled Functions must declare application authentication:
services:
functions:
enabled: true
auth:
type: applicationA deployed function that declares an audience on its RayfinContext receives a token for
the app identity in ctx.Tokens. In this function, the Rayfin data query runs as the caller,
and the Fabric REST call runs as the app:
import {
AudienceType,
UserDataFunctions,
type RayfinContext,
} from '@microsoft/fabric-user-data-functions';
export type AppSchema = {
Todo: { id: string; title: string };
};
const udf = new UserDataFunctions();
udf.func(
'todoCountAndWorkspaceCount',
async (
ctx: RayfinContext<AppSchema, AudienceType.Fabric>,
): Promise<{ todos: number; workspaces: number }> => {
// Runs as the signed-in caller: the Todo entity's policies apply.
const todos = await ctx.getDataClient().Todo.select(['id']).execute();
// Runs as the app identity: it must have access in Fabric.
const response = await fetch('https://api.fabric.microsoft.com/v1/workspaces', {
headers: { Authorization: `Bearer ${ctx.Tokens.Fabric}` },
});
if (!response.ok) {
throw new Error(`Fabric API returned ${response.status}`);
}
const body = (await response.json()) as { value: unknown[] };
return { todos: todos.length, workspaces: body.value.length };
},
[],
);Grant the app identity the permission each resource needs — for example a workspace role for Fabric REST calls, or a data-plane role for Azure Storage — and check that the Fabric API you call supports application identities. Connections lists the five supported audiences and what to grant for each.
Signing in to the app and being allowed to invoke a function work as before; application authentication only changes which identity the function uses for external resources.
Warning
During local development, the Functions host started by rayfin dev gets tokens for the
account you signed in with, not for the app identity. A call that works on your machine
can fail after rayfin up if the app identity lacks access. Grant the app identity
access before you deploy.
Connectors choose per connector
Each connector entry in rayfin.yml carries its own auth.type:
delegatedruns every query as the signed-in user, through an on-behalf-of token exchange. The user needs their own permission on the Fabric workspace and item, and@role()policies on generated connector entities can compareclaimsto the caller.applicationruns every query as the app identity. All users see the same data, so claims-based policies no longer distinguish users. It is allowed only forfabric-sqlanalytics,fabric-warehouse, andfabric-sqldatabase.
rayfin connector add writes auth.type: application for those three SQL types. To keep
per-user access, change the value to delegated before you deploy — and again after you
re-add the connector, because re-adding resets it. Semantic model connectors accept only
delegated. See Connector authentication.
Grant access at the source
Neither identity grants access by itself. Rayfin passes the identity through, and the target resource decides:
- For delegated calls, give each user — or a group they belong to — access to the Fabric workspace and item. A user without access gets an authorization error.
- For application calls, give the app identity — the owner of the Fabric app item — access to the resource.
- For your own entities, write permission decorators. No source grant is involved.
Choose a surface
| You need | Use |
|---|---|
| Per-user rows from entities your app owns | Entity policies on your own data |
| Per-user rows from an existing warehouse or SQL database | A SQL connector with auth.type: delegated |
| One shared view of an existing Fabric source | A SQL connector with auth.type: application |
| DAX over a semantic model, scoped to each user | A semantic model connector |
| Custom server-side calls to Fabric, OneLake, Azure Storage, Azure AI Foundry, or Azure DevOps | A function connection |
| Any other HTTPS API | A function that reads an API key from ctx.Secrets |
In my Rayfin project, help me decide which identity each server-side integration should use,
then configure it.
For data my app owns in rayfin/data, keep access caller-scoped with @role() or
@authenticated() policies — this also applies to ctx.getDataClient() inside functions.
For an existing Fabric warehouse, SQL database, or Lakehouse SQL endpoint, add a connector
and set auth.type in rayfin/rayfin.yml: delegated when each user should see only what they
are allowed to see at the source, application when every user should see the same data
through the app identity. Semantic model connectors must use delegated. For custom calls to
Fabric REST, OneLake, Azure Storage, Azure AI Foundry, or Azure DevOps, write a function
that declares the audience on RayfinContext and reads ctx.Tokens, with
services.functions.auth.type: application in rayfin.yml — those calls run as the app
identity, which is the owner of the Fabric app item.
Tell me exactly which permissions to grant to which identity on each resource, and which
files you changed.