Partner Token
Partner SSO lets a user who is already signed in to your platform reach NinjaTrader without a second login. Your backend signs a short-lived JWT identifying the user, exchanges it for a NinjaTrader access token, and the user arrives signed in. No NinjaTrader password is involved.
You provide identity. NinjaTrader provides everything else: authorization, account access, trading permissions, and billing.
How the Flow Works
- The user signs in to your platform, which you authenticate.
- The user chooses to open NinjaTrader from your application.
- Your backend issues a short-lived JWT identifying that user.
- Your backend posts the JWT to
oAuthTokenusing thejwt-bearergrant. - NinjaTrader verifies the assertion, resolves the user, and returns access and refresh tokens.
The assertion travels server to server, so it never appears in a URL, browser history, deep link, or any other user-visible channel. Only the resulting access token reaches the user’s device.
You operate two things: a JWT issuer that signs short-lived assertions, and a JWKS endpoint that publishes your current public keys. NinjaTrader operates verification and token issuance.
Prerequisites
Before writing code, send NinjaTrader the following so your platform can be registered. Provide one set per environment.
You also need to agree on the NinjaTrader user IDs for your users, and on the test users you will use before go-live. NinjaTrader confirms the exact audience URL per environment and the schedule for enabling your integration.
Signing the Assertion
Sign with RS256, the only algorithm currently supported. We recommend a 2048-bit key or larger; NinjaTrader does not enforce a minimum key size. The header must carry a kid that matches a key published at your JWKS URL.
Required claims:
Keep exp minus iat short. 60 seconds is recommended and 300 seconds is the hard cap, above which the assertion is rejected. NinjaTrader tolerates 60 seconds of clock skew on the time claims. Reusing a jti is rejected as a replay, so generate a fresh UUID for every assertion.
Any additional claims you include are ignored. Authorization is always determined from the resolved NinjaTrader user, never from your assertion.
For the full claim-by-claim requirements, see oAuthToken.
Mapping Users with the sub Claim
The sub claim identifies which NinjaTrader user is signing in. It must be the numeric NinjaTrader user ID of an account that already exists.
There is no partner-facing lookup API, so the mapping is established during onboarding by one of three routes: NinjaTrader provisions the accounts and returns the IDs, you provision through the Admin Dashboard and read the IDs there, or the two sides agree a one-time export to seed the mapping. Whichever route you use, you must already know the NinjaTrader user ID when you issue the assertion.
Store that ID alongside your own user record and read it at sign-in time.
Publishing Your Keys
NinjaTrader fetches your public keys to verify signatures, so your JWKS endpoint must be:
- Served over HTTPS. Plain HTTP is not fetched.
- Publicly reachable with no authentication, since it publishes public keys only.
- In standard JWKS JSON format, per RFC 7517.
Responses are cached for one hour. When an assertion arrives with a kid that is not in the cache, NinjaTrader refetches once before failing, which is what makes rotation work without coordination.
Rotating Keys
- Generate a new RSA key pair and give it a new
kid. - Publish the new public key alongside the old one.
- Switch your signer to the new key and its
kid. - Remove the old public key once every assertion signed with it has expired.
Rotate at least once a year, and keep both keys published for at least twice your maximum assertion lifetime so in-flight assertions still verify.
Exchanging the Assertion for a Token
application/x-www-form-urlencoded. This is a platform-wide convention across all NinjaTrader auth endpoints, so send Content-Type: application/json.A successful exchange returns:
Read the lifetimes from expires_in and refresh_token_expires_in rather than hard-coding them. They are environment configuration and there is no partner-specific override. An id_token is only issued on the authorization_code grant and is always null for jwt-bearer.
Refreshing a Session
Do not exchange a new assertion to refresh. Use the standard refresh-token grant on the same endpoint:
Refresh tokens are single-use and rotate: each refresh consumes the current token and returns a new one. Because a partner session is keyed on the partner and the user rather than on a device, a new jwt-bearer exchange replaces the refresh token issued by the previous exchange for that same user. Keep the most recent pair rather than holding several in parallel.
Handling Errors
HTTP 200 for successes and errors. Do not branch on the status code. Check whether access_token is present, or whether an error field is present, to determine the outcome.Only server_error is worth retrying. invalid_client, invalid_grant, access_denied, unsupported_grant_type, and bad_request all indicate configuration, consent, or assertion problems that will not resolve on their own. The exact error_description strings are listed on oAuthToken.
Security Practices
- Store signing keys in a secrets manager and restrict access to the signing service. Never log a private key.
- Issue one assertion per sign-in and keep
expat 60 seconds. Do not cache assertions for reuse. - Serve JWKS over TLS 1.2 or later, use 2048-bit keys or larger, and set
Cache-Control: max-age=3600. - If a signing key leaks, an attacker can forge assertions for any of your users. NinjaTrader can disable your configuration immediately, after which every assertion returns
invalid_client. Access tokens already issued remain valid until they expire, so notify NinjaTrader as soon as you suspect compromise.
Testing
Use separate registrations per environment. Before requesting production enablement, confirm each of these against staging:
When debugging, check the kid in your header against your published JWKS first, since a mismatch is the most common silent failure. Confirm that iat and exp are in seconds rather than milliseconds, and read error_description, which identifies the validation that failed.
Sample Code
Generate a 2048-bit key pair:
Sign an assertion, with PyJWT and cryptography installed:
Build the JWKS JSON to publish, with cryptography installed:
During rotation, run this for each active key with a distinct kid and combine the resulting keys arrays into one response.
Going Live
- Production registration created, initially disabled until go-live.
- JWKS endpoint reachable from the public internet over HTTPS.
- Production signing key generated, private key secured, public key published.
- NinjaTrader user IDs obtained and mapped for your first cohort.
- All staging cases above passing.
- Error handling implemented for each case, and failed issuance attempts logged on your side for support.
- Refresh flow integrated, without re-issuing assertions to refresh.
- Go-live time coordinated with NinjaTrader to enable the production configuration.
Common Questions
A valid assertion returns “User not found”. The ID in sub matches no NinjaTrader user. Confirm the user is provisioned under your organization. Usual causes are a formatting difference, a staging ID used against production, or a user who has not been onboarded.
Does a logout on my platform end the NinjaTrader session? No. Sessions are independent. To end both, call the NinjaTrader logout endpoint with the access token.
Can users still sign in directly? Yes. Partner SSO is enabled per organization and does not block normal sign-in.
How do I report a security issue? Use the security channel established during onboarding. If you suspect key compromise, ask for your configuration to be disabled while you rotate.
Next Steps
- Embedding the Funding Page with an iframe: put a NinjaTrader-hosted funding page inside your own application.
- SSO with ShortGrantCode: redirect a signed-in user to a NinjaTrader-hosted page.

