How to use getUser() with non-admin JWT credentials?

I am running a Canton sandbox ledger, with authentication enabled.

I am trying to run the following test script, as a non-admin, authenticated user:

testAuth : Script()
testAuth = script do
  userId <- validateUserId("alice")
  user <- getUser userId
  let party = fromSome user.primaryParty

  submit party do
      createCmd ToTransfer with
        owner = party
  
  pure()

If I use the following JWT payload, everything works fine:

{
  "https://daml.com/ledger-api": {
    "admin": true,
    "actAs": [
      "Alice::12200866ee1c884b247df0e8a32f23012bcb22676c220130ecf460029eab5a0aa588"
    ],
    "readAs": [
      "Alice::12200866ee1c884b247df0e8a32f23012bcb22676c220130ecf460029eab5a0aa588"
    ]
  }
}

If I remove the admin rights:

{
  "https://daml.com/ledger-api": {
    "actAs": [
      "Alice::12200866ee1c884b247df0e8a32f23012bcb22676c220130ecf460029eab5a0aa588"
    ],
    "readAs": [
      "Alice::12200866ee1c884b247df0e8a32f23012bcb22676c220130ecf460029eab5a0aa588"
    ]
  }
}

I get a PERMISSION_DENIED. The ledger logs:

INFO  c.d.p.a.s.ApiLedgerIdentityService - Received request for ledger identity: GetLedgerIdentityRequest(), context: {participant: "sandbox"}
WARN  c.d.l.a.a.Authorizer - PERMISSION_DENIED(7,0): Claims do not authorize the use of administrative services., context: {participant: "sandbox", err-context: "{location=Authorizer.scala:218}"}
INFO  c.d.c.n.g.ApiRequestLogger:participant=sandbox tid:3be57a5b8d84351a5baa004319fee105 - Request c.d.l.a.v.a.UserManagementService/GetUser by /127.0.0.1:42252: failed with PERMISSION_DENIED/An error occurred. Please contact the operator and inquire about the request <no-correlation-id>

The documentation of getUser() states

Required authorization: HasRight(ParticipantAdmin) OR IsAuthenticatedUser(user_id)

So it seems that there should be a way to make it work, but I cannot figure it out.

Maybe something about the value of user-id. I have tried to add "alice" in the actAs, readAs sections, but without success.

There are two different token formats.

  1. The old custom claims format you’re using where you specify admin, actAs and readAs. In that case you can never auth as a user id so admin claims are the only option for doing what you want.
  2. The new user access tokens. In those, you just specify the user id in your token and you have rights to query info that user as well as whatever claims that user has.

So you want the second option to do what you want, i.e., a token like

{ "sub": "youruserid",
  "scope": "daml_ledger_api"
}

Ah, ok, it does makes more sense.
It is working fine, and it simplifies things.

One follow up question, I had to modify the applicationId to match the user_id, otherwise I was getting an error.

WARN  c.d.l.a.a.Authorizer - PERMISSION_DENIED(7,0): Claims are only valid for applicationId 'alice', actual applicationId is 'integration-tests', context: {participant: "sandbox", err-context: "{location=Authorizer.scala:218}"}

Is there a way to get around this ? Not being able to use a different applicationId might become a problem.

I don’t think you can get around this. The app id is set to the user id for those tokens with no overwrite.

OK. I think we can work with that.

It is a matter to use the user account more like a service account or an application account, then it makes sense that things have to be consistent.