In my API I'm looking to have certain API users wh...
# support-questions-legacy
d
In my API I'm looking to have certain API users whose tokens never expire. Ideally this would be a different type of user that doesn't require a thirdparty login (which all "real" users need), but rather some user type that can be configured. Basically, I want an organization (which is a concept I have in my own DB) to be able to get an API access token to use for them to send requests. Unlike user tokens I don't want them to have to deal with e.g. refreshing tokens. What would be the best way to do that?
r
hey @da.fant you can see our m2m guide
d
Got it, thx! 1. Is there a way to revoke these tokens (like revoking sessions for users) 2. Is there a native way to do e.g.
get_roles_for_user
,
add_role_to_user
, etc? I'm assuming not because this isn't a user, it's just a token that doesn't have any concept of user/permissions
This is maybe a dumb idea, but thoughts on this approach? 1. Enable social login + email login on backend 2. Frontend only exposes social login 3. When creating an organization token, we register a organization/token user with e.g.
{token_id}@tokens.example.com
with some pass stored in our DB (e.g.
Token(organization_id, password)
) 4. Then each org can have multiple token users, and permissions can be regulated on a per-user level
I guess this approach would have problems with requiring to refresh tokens
r
> Is there a way to revoke these tokens (like revoking sessions for users) Not in an in built way, but you can implement this on your own by keeping track of these in your db. > This is maybe a dumb idea, but thoughts on this approach? Instead of this, you could just create session tokens using the org ID directly using the Session.createNewSession function. You don't need to create fake email password users for these. You can even define roles etc for the org ID you have which will get set into the claims of the JWT If you want the session tokens of the org to be VERY login lived, but the session tokens of regular users to be short lived, you can do the following: - You can create a regular session using Session.createNewSession function. - Then copy over the claims of the access token and create a JWT using the JWT recipe with the claims and any custom lifetime (several years even). This JWT would be the access token for the org, and it would contain all the same claims as a regular session. This also means that you can use verifySession / getSession to validate these JWTs.
I assume you don't want to just use the JWT recipe cause you want stuff like roles etc to be reflected in the claims right?
and those roles are associated with the org ID right?
d
Ahh, that's very nice! Managed to create a new user by just making up the user id and creating a session. Not sure what you mean with "copy over the claims of the access token..." Atm this is the payload of the access token:
Copy code
{
  "sub": "my-custom-arbitrary-id",
  "exp": 1687442684,
  "iat": 1687439084,
  "sessionHandle": "a8c22f92-dc04-4d4d-968d-a371849300fa",
  "refreshTokenHash1": "2936c8c0c0ad8bb3fa1a1d892a018bfb6b9fd0af4331b060f68c40b95684e1c2",
  "parentRefreshTokenHash1": null,
  "antiCsrfToken": null,
  "iss": "http://localhost:8000/auth"
}
I ideally want to use something similar to what I'm currently doing:
Copy code
python
@router.post('...')
async def endpoint(
  ...,
  organization_id: UUID,
  auth_session: SessionContainer = Depends(verify_session()),
) -> ActionRunListItem:
  await permissions.assert_permission(
    auth_session.get_user_id(),
    organization_id,
    'organization.write',
  )
  # ...

async def assert_permission(user_id: str, organization_id: UUID, permission: Permission):
  roles = (await get_roles_for_user(user_id)).roles
  # logic to check if any roles have the permission
Would you do some fn similar to
verify_session
that does something like the /like-comment example here? https://supertokens.com/docs/microservice_auth/jwt-verification/index Basically, wrap the current verify session, and if that returns no session we check if the bearer token resolves to this custom "API user id" we created and in that case return that user id, session, etc
Also, any reason these users don't show up in the user dashboard? They're registered in the supertokens DB (e.g. in the user_last_active table)
r
When I say copy over the claims, I meant that when you use JWT.createJwt, you can give it a payload object. Do that , you can get from the session’s access token payload
The reasons these users don’t show up on the dashboard is cause the dashboard only shows users who sign up on supertokens. These custom user ids are not a part of that
d
Do that , you can get from the session’s access token payload
- what does this mean? You mean I'd feed in the different fields from the JSON dump above (e.g. sub, sessionHandle, but not e.g. exp)?
r
Yes. Exactly.
And use the JWT.createJwt function to make a JWT that’s alive for however long you want
Independent of the session recipe
d
@rp_st exactly what fields need to be copied over? Atm the following scenario passes
verify_session
(created with
create_new_session_without_request_response
)
Copy code
json
{
  "sub": "sana-labs",
  "exp": 1687459722,
  "iat": 1687456122,
  "sessionHandle": "b6442d9b-3cae-470f-bbbc-79a7874fe825",
  "refreshTokenHash1": "e04bdb94ae7c1d0cdffb7d86fb9be2d57ee93ae85e61bca4e12c608208f1bd89",
  "parentRefreshTokenHash1": null,
  "antiCsrfToken": null,
  "iss": "http://localhost:8000/auth"
}
```json

But the following doesn't:
```json
{
  "sub": "sana-labs",
  "source": "microservice",
  "sessionHandle": "b6442d9b-3cae-470f-bbbc-79a7874fe825",
  "iat": 1687456122,
  "exp": 4841056122,
  "iss": "http://localhost:8000"
}
This is the code:
Copy code
python
user_id = "sana-labs"
    session = await create_new_session_without_request_response(user_id)
    jwtResponse = await asyncio.create_jwt({
      'source': 'microservice',
      'sub': session.get_user_id(),
      'sessionHandle': session.get_handle(),
    })
iss is different - not sure if that matters
r
All fields need to be copied over except of the iat and exp
And on top of that, you can add custom other fields
d
How can I get a list of all users? Basically I'll create my "API users" with user id
organization:{organization_id}:{token_id}
where token_id is just some UUID. I want to list all of an organization's API users, which basically is all users where id starts with
organization:{organization_id}:
Searched in the API ref but couldn't find it: https://app.swaggerhub.com/apis/supertokens/CDI/3.0.1
r
We do have user pagination functions. Search our docs for user pagination
But that API won’t give you IDs for session users which haven’t signed up on supertokens
d
Got it! I'm still running into some issues: 1. Original session token 2. Raw JWT token I've even tried running with the raw payload from the original token when creating the raw JWT token, but am still getting unauthorized
Copy code
python
async def create_organization_token(organization_id: UUID) -> str:
  user_id = f"organization:{organization_id}:{uuid4()}"
  session = await create_new_session_without_request_response(
    user_id,
    session_data_in_database={ 'organization_id': str(organization_id) }
  )
  session_access_token = session.get_access_token()
  session_access_token_data = jwt.decode(session_access_token, options={"verify_signature": False})
  del session_access_token_data['iat']
  del session_access_token_data['exp']

  # jwt_response = await jwt_asyncio.create_jwt({
  #   'source': 'organization_token',
  #   **session_access_token_data,
  # })
  jwt_response = await jwt_asyncio.create_jwt({
    "sub": "organization:614a7d4c-a1c3-4ebc-bd39-df90c503a264:0a16471f-69dc-48b3-bdee-8a8b713b0f4c",
    "exp": 1687467128,
    "iat": 1687463528,
    "sessionHandle": "c00e7b86-653a-43e0-8f29-3750a6f21044",
    "refreshTokenHash1": "27eb9e16c0a430ef4678508c002863fc0b8d76b136f8a955e636e23789077a34",
    "parentRefreshTokenHash1": None,
    "antiCsrfToken": None,
    "iss": "http://localhost:8000/auth"
  })
  if not isinstance(jwt_response, CreateJwtOkResult):
    raise Exception("Unable to create organization token")
  
  return jwt_response.jwt
Any ideas?
algo is different ant iat/exp is different
r
Algo is different?
The only difference should be in iat and exp. Is that not the case?
d
I mean in the screenshots when pasting the tokens into jwt.io, the headers are different
r
Copy code
python
    s = await create_new_session_without_request_response("userId", {}, {})

    payload = s.get_access_token_payload()
    del payload["iat"]
    del payload["exp"]

    now = get_timestamp_ms()
    # expiry jwt after 10sec
    jwt_expiry = now + 10 * 1000
    jwt = await create_jwt(payload, jwt_expiry, use_static_signing_key=False)
    s_ = await get_session_without_request_response(jwt.jwt)
The above is what should work
if it doesn't please upgrade to the latest python SDK version.
and you can set the
jwt_expiry
to anything, as well as add custom claims to
payload
before passing it into
create_jwt
d
Ok, that started working after upgrading from 0.14.0 to 0.14.5. However, now I'm getting
try refresh token
error 1. shows original token (which works) 2. shows the jwt token with long expiry (where
jwt.jwt
and
s_.get_access_token()
in your code above have the same value) Differences: 1. header in original token has
version: 3
2. iat/exp 3. ordering of fields Any idaes?
r
Oh. That’s odd. Can you enable backend debug logs and then call the function? What’s the output of the logs?
d
Very strange - it's working now. I tried multiple times, but maybe consistently copy/pasted wrong? Anyhow - appreciate the patience and help!
The primitives of supercore are nice - it's great how these kinds of use cases can be created by combos of primitives (although it's a bit tricky to combine them without much context)
r
Yeaaaa. Sometimes uses cases are niche, and we don’t have docs for them
18 Views