Skip to content

Conversation

arnavdas88
Copy link

@arnavdas88 arnavdas88 commented Jul 28, 2025

Added Pythonic Interface for creating Account Claims and User claims as JWT.

Issue: Issue 717

Replicated the example at NATS by Example - Programmatic NKeys and JWTs in the test file tests/contrib/test_jwt.py.

Example Code Snippet

operatorKP = from_seed(b'SOALU7LPGJK2BDF7IHD7UZT6ZM23UMKYLGJLNN35QJSUI5BNR4DJRFH4R4')
accountKP = from_seed(b'SAALXUEDN2QR5KZDDSH5S4RIWAZDM7CVDG5HNJI2HS5LBVYFTLAQCOXZAU')
userKP = from_seed(b'SUALJTG5JNRQCQKFE652DV4XID522ALOHJNQVHKKDJNVGWHCLHOEXEROEM')

account_claims = Claims(
    name="my-account",
    jti="PBFES33GGIFZM6UGC7NY5ARHRBFVFU4UD7FS2WNLZH3KPGWFVEFQ",
    iat=1678973945,
    iss=operatorKP.public_key.decode(),
    sub=accountKP.public_key.decode(),
    nats=Account(
        limits=OperatorLimits(
            nats_limits=NatsLimits(data = -1, payload = -1, subs = -1),
            account_limits=AccountLimits(exports = -1, imports = -1, wildcards = True, conn = -1, leaf = -1),
            jetstream_limits=JetStreamLimits(disk_storage=-1, mem_storage=-1)
        ),
        default_permissions=Permissions(),
        generic_fields=GenericFields(version=2, type=Types.Account)
    )
)

account_claims.to_dict()

Some issues with the PR

  • Directory and File structure is a mess in the contrib/.
  • Test for Operator Claim is not written yet as the operator claims are not clear in NATS by Example - Programmatic NKeys and JWTs blog.
  • encode_user_claim() and encode_account_claim() is not yet mature and only exists in the test file.
  • The tests pass with the example shown in NATS by Example - Programmatic NKeys and JWTs, but the resultant jwt has not been tested with a nats-server.
  • Adding code documentation can be helpful.

Note : Directory and File structure is a mess in the contrib/ because following any other file structure was resulting in circular import error, and putting all the code in a single file was making it hard to read. Any suggestions / commits on how to have a better structure for the files in contrib/ directory are welcome!

@arnavdas88 arnavdas88 requested a review from GarraPeters August 18, 2025 15:11
@Jarema Jarema requested review from caspervonb and removed request for GarraPeters August 19, 2025 11:18
@arnavdas88
Copy link
Author

Some (foreign and unrelated to this PR) tests in test_js.py are failing. Can someone confirm this behaviour?

@caspervonb @GarraPeters @wallyqs

@alexbozhenko
Copy link
Member

The test was fixed in #728. Could you please rebase?

@arnavdas88
Copy link
Author

arnavdas88 commented Sep 5, 2025

Merged! Now all the tests should pass! ✅

@caspervonb
Copy link
Collaborator

caspervonb commented Sep 16, 2025

Looked over this a few times over the past weeks but conflicted about the somewhat large surface area it covers (sort of feels like it should be its own package).

Me and @wallyqs will discuss this offline.

@carlosjgp
Copy link

Any progress on this @caspervonb ? My company is waiting for this change to implement NATS Auth Callout

@caspervonb
Copy link
Collaborator

caspervonb commented Sep 30, 2025

Yeah so since this doesn't actually touch anything in the nats package, I've been thinking to turn this into its own namespace package as I have multiple pending pull requests that take the same approach, e.g nats-jwt or something along those lines.

How do you feel about that @arnavdas88? I'd probably open a derivative PR doing it.

@arnavdas88
Copy link
Author

Yes @caspervonb , That sounds great. I have made some updates as well that I have yet to commit. I will directly push the updated code in the new namespace then.

@arnavdas88
Copy link
Author

@carlosjgp In the meanwhile, you can try using the arnavdas88:jwt-claims for testing as well as to develop on top of it, locally. Once the new namespace is ready, you will just have some minimal changes to make it work.

That will also give us time to add additional functionalities, or change the structure if necessary as well as help in making the jwt-claims PR, more mature for developers to use.

I believe, you can install the nats.py@jwt-claims using the below pip command:

pip install git+https://github.com/arnavdas88/nats.py.git@jwt-claims

@caspervonb
Copy link
Collaborator

caspervonb commented Oct 4, 2025

Started to clean this up over in #740 but I just have to ask, is this AI generated @arnavdas88?

Some problematic patterns that became immediately apparent when starting to clean up:

  1. **FlatteningModel - Custom serialization logic that reimplements dataclass functionality. The singleton() method and flattening logic is overcomplicated.

  2. Redundant constructors - Models like Claims, Account, and User have manual __init__ methods that just duplicate what @dataclass would do automatically.

  3. Inconsistent dataclass usage - Some models use @dataclass (like WeightedMapping, Info), others use manual classes with custom __init__. Pick one approach.

  4. utils.py - Contains a full copy-paste of Python's stdlib asdict() function just to add an omitempty parameter.

  5. Empty pass-through classes - UserClaims, AccountClaims, OperatorClaims do nothing.

  6. Tests manually construct JWTs instead of using the library's own code.

  7. Constants etc what wouldn't pass lint.

  8. Empty scaffolding files

@arnavdas88
Copy link
Author

@caspervonb

1.) The Flattening Model is indeed over complicated (I have already written that in the PR). Also, it cannot load a dict / json right now. It can only convert the dataclass to a dict.

I have an alternate implementation that makes it a little bit simpler, while adding both serialization and serialization. But it will still add some complications.

2.) There are Redundant Constructors because I took a lot of reference code from the golang package, as well as some code (for the constants and the jwt encoding and decoding) from the javascript/typescript package.

For the Redundant Code as well as the Flattening Model, I am trying to keep the code format and classes as consistent as possible with the golang code.

3.) About the inconsistent dataclass approach, dataclasses does make it easier for us to develop, but when inheriting multiple dataclasses, the classes will not have the nested structure (that is possible with golang's struct), and instead become a flat class (all the attributes are flat. I wanted to avoid that.

Currently the classes that do not need to inherit any other model (source reference from go package) is replaced by dataclasses, whereas any model that inherits other classes use the __init__.

4.) utils.py is indeed a complete copy paste of the asdict() function with the additional omitempty. The other option was to dynamically injection this behaviour. Making it a bad approach. As I said, I have an alternative approach. I want to keep the asdict as similar as possible to the original function.

5.) Empty classes for claims - They are mostly used as placeholder right now, as most of the behaviour for the claims is a bit confusing to me! Specially the OperatorClaims. The placeholder can be modified by someone more experienced in the claims part.

6.) Manual JWTs were constructed because that part of the code was referenced from the typescript/javascript code where the jwt were manually constructed. It was necessary as the jwt the algorithm used in the jwt for claims encoding doesn't usually fall under the standard algorithms, and may or may not raise issues. Hence I kept those functions custom.

7.) Constants doesn't only not pass lint, most of the constants are not even used. But they exist and may help to fetch specific data from NATS server. So I kept all of them! The constants exist in js/ts package but not in the python package. I wonder why!

Would love to know your opinion on these!

@caspervonb
Copy link
Collaborator

caspervonb commented Oct 7, 2025

Did anyone interested in this evaluate https://pypi.org/project/nats-jwt for this purpose, and if so what where did you find it fall short?

@arnavdas88
Copy link
Author

I didn't know this existed. Looks like nats-py-jwt is the source GitHub.

It looks like they can use some help with the tests. But overall the structure is pretty pythonic. It should work for most of the use cases. I'll give it a try ⭐!

@caspervonb
Copy link
Collaborator

Maybe the route to go with this is to lend a hand over there, looks like they've gotten pretty far.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants