Flutter jsonwebtoken generate Appstore Connect API token invalid - flutter

I am trying to generate a Appstore Connect API token to manage my apps. The package I am using is dart_jsonwebtoken. The code:
final jwt = JWT({
"iss": "YOUR_ISSUER_ID",
"iat": DateTime.now().millisecondsSinceEpoch / 1000,
"exp": DateTime.now().add(Duration(days: 30)).millisecondsSinceEpoch / 1000,
"aud": "appstoreconnect-v1",
"scope": [
"GET /v1/apps?filter[platform]=IOS"
]
}, header: {
"alg": "ES256",
"kid": "YOUR PRIVATE KEY ID",
"typ": "JWT"
});
// Here I use the content from AuthKey_KEYID.p8 file
final token = jwt.sign(ECPrivateKey('''-----BEGIN PRIVATE KEY-----
YOUR AuthKey Content
-----END PRIVATE KEY-----'''), algorithm: JWTAlgorithm.ES256);
print('Signed token: $token\n');
The token is generated successfully, and I am using it to call the API, but I am getting an invalid token error:
{
"errors": [
{
"status": "401",
"code": "NOT_AUTHORIZED",
"title": "Authentication credentials are missing or invalid.",
"detail": "Provide a properly configured and signed bearer token, and make sure that it has not expired. Learn more about Generating Tokens for API Requests https://developer.apple.com/go/?id=api-generating-tokens"
}
]
}
I am pretty sure there is some wrong with my code to generate the token. Can somebody help me with that? Thanks in advance.
I got it working by using below ruby code. The generated token is working fine. I am trying to rewrite it using flutter.
require "base64"
require "jwt"
ISSUER_ID = "2ece1127-df4d-426a-a3eb-061e68f5eebe"
KEY_ID = "W9M2AM584Y"
private_key = OpenSSL::PKey.read(File.read('AuthKey_W9M2AM584Y.p8'))
token = JWT.encode(
{
iss: ISSUER_ID,
exp: Time.now.to_i + 20 * 60,
aud: "appstoreconnect-v1"
},
private_key,
"ES256",
header_fields={
kid: KEY_ID }
)
puts token

Related

Oauth2 Flutter Authorization Parameters

I have a (possibly?) niche question about Oauth2 in Dart and Flutter (I'm using the standard Oauth2 package: https://pub.dev/packages/oauth2). Specifically, I need some values that are passed back as part of the Authorization flow, but they are not the standard values, so it's not returned as part of the client.
Specifically, as part of my flow, I run:
final returnValue = await authClient.authenticate(
authorizationUrl: authorizationUrl,
redirectUri: redirectUri!,
);
No problem there. Then I run:
client = await grant
.handleAuthorizationResponse(Uri.parse(returnValue).queryParameters);
Again, works properly, returns a Client that I can use. However, following this function down, handleAuthorizationResponse calls _handleAuthorizationResponse. The function _handleAuthorizationResponse ends with:
var response =
await _httpClient!.post(tokenEndpoint, headers: headers, body: body);
// print(response.headers);
// print(response.body);
var credentials = handleAccessTokenResponse(
response, tokenEndpoint, startTime, _scopes, _delimiter,
getParameters: _getParameters);
return Client(credentials,
identifier: identifier,
secret: secret,
basicAuth: _basicAuth,
httpClient: _httpClient,
onCredentialsRefreshed: _onCredentialsRefreshed);
This is where my issue is. That response has some fields in the body that I need. They're not standard, so they're not passed back as part of the Client. Without rewriting my own versions of these functions, is there another way I can get access to these values?
In answer to a question, this uses a SMART on FHIR launch (it's a launch framework that's basically just an oauth2 wrapper) - it's standard in healthcare. The return json from the accessToken would look something like this:
{
"need_patient_banner": true,
"smart_style_url": "https://smart.argo.run/smart-style.json",
"patient": "87a339d0-8cae-418e-89c7-8651e6aab3c6",
"token_type": "Bearer",
"scope": "launch/patient patient/Observation.rs patient/Patient.rs",
"expires_in": 3600,
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuZWVkX3BhdGllbnRfYmFubmVyIjp0cnVlLCJzbWFydF9zdHlsZV91cmwiOiJodHRwczovL3NtYXJ0LmFyZ28ucnVuLy9zbWFydC1zdHlsZS5qc29uIiwicGF0aWVudCI6Ijg3YTMzOWQwLThjYWUtNDE4ZS04OWM3LTg2NTFlNmFhYjNjNiIsInRva2VuX3R5cGUiOiJiZWFyZXIiLCJzY29wZSI6ImxhdW5jaC9wYXRpZW50IHBhdGllbnQvT2JzZXJ2YXRpb24ucnMgcGF0aWVudC9QYXRpZW50LnJzIiwiY2xpZW50X2lkIjoiZGVtb19hcHBfd2hhdGV2ZXIiLCJleHBpcmVzX2luIjozNjAwLCJpYXQiOjE2MzM1MzIwMTQsImV4cCI6MTYzMzUzNTYxNH0.PzNw23IZGtBfgpBtbIczthV2hGwanG_eyvthVS8mrG4",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb250ZXh0Ijp7Im5lZWRfcGF0aWVudF9iYW5uZXIiOnRydWUsInNtYXJ0X3N0eWxlX3VybCI6Imh0dHBzOi8vc21hcnQuYXJnby5ydW4vL3NtYXJ0LXN0eWxlLmpzb24iLCJwYXRpZW50IjoiODdhMzM5ZDAtOGNhZS00MThlLTg5YzctODY1MWU2YWFiM2M2In0sImNsaWVudF9pZCI6ImRlbW9fYXBwX3doYXRldmVyIiwic2NvcGUiOiJsYXVuY2gvcGF0aWVudCBwYXRpZW50L09ic2VydmF0aW9uLnJzIHBhdGllbnQvUGF0aWVudC5ycyBvZmZsaW5lX2FjY2VzcyIsImlhdCI6MTYzMzUzMzg1OSwiZXhwIjoxNjY1MDY5ODU5fQ.Q41QwZCEQlZ16M7YwvYuVbUP03mRFJoqRxL8SS8_ImM"
}
So it has the typical values of an accessToken (expires_in, token_type, etc), but it also has things like 'patient'. Those are the values that I need.
In my example, the final Credentials (client.credentials looks like this):
{
"accessToken": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1cm46b2lkOmZoaXIiLCJjbGllbnRfaWQiOiIzZmE1Y2FmOS04YTk4LTQ4MjgtOTJkZS01OWU2NmJjYjIwNjQiLCJlcGljLmVjaSI6InVybjplcGljOk9wZW4uRXBpYy1jdXJyZW50IiwiZXBpYy5tZXRhZGF0YSI6IjVYWnFRU0lrSk9PNi1XRkhpVXBiMlg2ak5MQzJ1aDFQdWtaSHVWSHkzcTdJMTBBT1BfYXR5V0tEb19LMlRFRF9ic21TNk9UTmZiVGtISnY1dnFvM1RtTVduRlk2RDBPSlE2WkRhd1NJWkk4WDh0Xy1XT1pYWEs1WjFrcTNBNm9mIiwiZXBpYy50b2tlbnR5cGUiOiJhY2Nlc3MiLCJleHAiOjE2NTQ2NDEzMDIsImlhdCI6MTY1NDYzNzcwMiwiaXNzIjoidXJuOm9pZDpmaGlyIiwianRpIjoiMDg1ZTdhNTYtY2I0OS00Zjg3LWFiYmEtZDg0M2ZmODI2YmQ2IiwibmJmIjoxNjU0NjM3NzAyLCJzdWIiOiJlYjRHaWE3RnlpanRQbVhrcnRqUnBQdzMifQ.rAKweImVE86oF3ciZDGhDysrYY9-XV6fBbyzqkQiJxHg-V-zImW414m3X5wKcP9B0J1MMdJCwg5DTpcbd0iU-N3SXRVXxBO2BqTcMAGLr-jlepnqBfu1Esg0nAI9jVasSWhz6tXFcLWOoCocg1hLcMfY875xnszwztJiJieDhumKZSStcsQM4KR9lUQZdJ3-U6IXV7wn3kaD4GQBSPZ0OkUe2d8zdCpjcbGCO-wWNdfe_sQDd7k7MbBJ1ryFRtd45GSzhKFa3Cch8kWTo3bGPlzzFuvhX_kbX1WtqTXaeB2G-o49lT4RJldnZi62L51VtS69_M15EsQtmMRHg6WMEA",
"refreshToken": null,
"idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6InRvVzlqTVVTTi81L0wzaXdhUUdkVG1ORHVodnAvSmNBWlZIL2NPSjZPckU9IiwidHlwIjoiSldUIn0.eyJhdWQiOiIzZmE1Y2FmOS04YTk4LTQ4MjgtOTJkZS01OWU2NmJjYjIwNjQiLCJleHAiOjE2NTQ2MzgwMDIsImZoaXJVc2VyIjoiaHR0cHM6Ly9maGlyLmVwaWMuY29tL2ludGVyY29ubmVjdC1maGlyLW9hdXRoL2FwaS9GSElSL1I0L1BhdGllbnQvZXJYdUZZVWZ1Y0JaYXJ5VmtzWUVjTWczIiwiaWF0IjoxNjU0NjM3NzAyLCJpc3MiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyIiwic3ViIjoiZXJYdUZZVWZ1Y0JaYXJ5VmtzWUVjTWczIn0.hh33_q4f3tnioB7Iq6jY07-m5i_OsaqUt_kg_ZnPMGPKK8AnYVk3Tps2XTdUzUIHizFRWlGmAT_E0F283LBmVPTrbtD_X6EwqmUbTBrWj94RyvE-k3ofoEo-CwbSJZXu8MrQTb3DzpRKTGo7D1sI5E4UqnKQhPWFmhwCjMXpbdRy6bddb14fdWZzjS_Ffq4OsNRIalnePR8z1zNtSy14_RCiSh8o2elkj3p1AOmSXeD9-nZ91Z646lt4C5oP9gwN7OhmBovQRuDYaql1tz1aHOhilIsBZc1jMxEZJ65cekmFy6HZ4rME23xg-EQHu7XhKWOpOjovbMPwapSlC-eUcA",
"tokenEndpoint": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2/token",
"scopes": [
"patient/Patient.read",
"patient/Questionnaire.read",
"patient/QuestionnaireResponse.Read",
"fhirUser",
"launch/patient",
"openid",
"profile"
],
"expiration": 1654641291566
}
I've tried decoding the accessToken and the idToken and neither of them have fields like "patient". Howevever, if after I get the response and print the body, it looks like this:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1cm46b2lkOmZoaXIiLCJjbGllbnRfaWQiOiIzZmE1Y2FmOS04YTk4LTQ4MjgtOTJkZS01OWU2NmJjYjIwNjQiLCJlcGljLmVjaSI6InVybjplcGljOk9wZW4uRXBpYy1jdXJyZW50IiwiZXBpYy5tZXRhZGF0YSI6IkJJR1dLcDZPbU1ZcmJkT2YzMkFvX0djRmtiUG1RbEtZcHdEN1d4S0VLNEJXQTZvSUxXVUdRd05SUWdtMUdsd00xTzE0YUVZNi1GTmd6Si1jMGRiQ0puODRIUEU4SDEzNmp6UzNCZGFDUVBMSXFyYXpZenF3Uldyd1MwX3NWU1k5IiwiZXBpYy50b2tlbnR5cGUiOiJhY2Nlc3MiLCJleHAiOjE2NTQ2NDc2MDgsImlhdCI6MTY1NDY0NDAwOCwiaXNzIjoidXJuOm9pZDpmaGlyIiwianRpIjoiMTA1OTFmOTMtYjUxMi00MmNlLTk1YjgtYjY2NTEwODM0MzNkIiwibmJmIjoxNjU0NjQ0MDA4LCJzdWIiOiJlYjRHaWE3RnlpanRQbVhrcnRqUnBQdzMifQ.AJOd9g8YAJp91n0qY3Hg9F2sNpo26VMYKpNKR5y7CIV8zrADh2whv2WRm8gi-cIeS6XUR6UzXyzXVJ9Ips5FgFdIZ4yQI_HXxH9r8aeF6VS6jT-ZQygtzWnVYeyJvu-1b3YpbgdCd3KTrnWLwhU3vqUmil2L8gJzWG473ihXDz-7ezsJBBl9R-c5Ap_L6WF6Ox8lHH6mgwbZHeKr0U0aYne-QLM7mylsPC5BC_WlUOwMnEJ73DKjF2E0X6wMCP7jMieJxhpkTIDRwKQbuGwLjtneS-Efu69NHGsxSP_m3aN652rdh9-b5WyIsT-DqjPHxHTtbxGQI-WthHOhnLaDkQ",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "patient/Patient.read patient/Questionnaire.read patient/QuestionnaireResponse.Read fhirUser launch/patient openid profile",
"__epic.dstu2.patient": "TnOZ.elPXC6zcBNFMcFA7A5KZbYxo2.4T-LylRk4GoW4B",
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6InRvVzlqTVVTTi81L0wzaXdhUUdkVG1ORHVodnAvSmNBWlZIL2NPSjZPckU9IiwidHlwIjoiSldUIn0.eyJhdWQiOiIzZmE1Y2FmOS04YTk4LTQ4MjgtOTJkZS01OWU2NmJjYjIwNjQiLCJleHAiOjE2NTQ2NDQzMDgsImZoaXJVc2VyIjoiaHR0cHM6Ly9maGlyLmVwaWMuY29tL2ludGVyY29ubmVjdC1maGlyLW9hdXRoL2FwaS9GSElSL1I0L1BhdGllbnQvZXJYdUZZVWZ1Y0JaYXJ5VmtzWUVjTWczIiwiaWF0IjoxNjU0NjQ0MDA4LCJpc3MiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyIiwic3ViIjoiZXJYdUZZVWZ1Y0JaYXJ5VmtzWUVjTWczIn0.wxlvguGhAZdWJiSpX1-jzANXk0hFhLeIPFS5BlnIJLLZg8ibvpzLutQr2Z7Rg_d07_amI4gGbNigso9gvPbN5e1jjDGZkU2QYUbcLZbwkTcxXfVWOsyAADOZZrqx0J1yrGIeA4V4EfqQ4xBym_e8CeEjGP9L4ouRBKK6AHR5N5Mmdo_I4_RoPr-mCR2e2Q_of7tYFuhcl8mHaT6brbn-ZoEuAMgAQztF-7SBpDSvRB1C4HzV6mk-Hql0jNhZ0WefZe_ve0gB3exdWDjCLClpRRjt_MRaFTYGPqiZuyJF-dEFEqNar1Y5BRjQmUdJbDWj8ecfWaldigXNVAvNthbs4g",
"patient": "erXuFYUfucBZaryVksYEcMg3"
}
So you can see why I need access to that information. Any idea, apart from extending the class and overriding the functions I could get access to it?
access_token contains all the information that is in the response.
{
"need_patient_banner": true,
"smart_style_url": "https://smart.argo.run//smart-style.json",
"patient": "87a339d0-8cae-418e-89c7-8651e6aab3c6",
"token_type": "bearer",
"scope": "launch/patient patient/Observation.rs patient/Patient.rs",
"client_id": "demo_app_whatever",
"expires_in": 3600,
"iat": 1633532014,
"exp": 1633535614
}
Access token is not meant to be read by the client, but ID token is for that. Defining the right scopes in authentication request will also return ID token.
Client has a credentials property, which holds all the tokens.
id_token's sub field seems to match patient. There is also fhirUser user endpoint, which probably gives you more information about the patient.
{
"aud": "3fa5caf9-8a98-4828-92de-59e66bcb2064",
"exp": 1654644308,
"fhirUser": "https://fhir.epic.com/interconnect-fhir-oauth/api/FHIR/R4/Patient/erXuFYUfucBZaryVksYEcMg3",
"iat": 1654644008,
"iss": "https://fhir.epic.com/interconnect-fhir-oauth/oauth2",
"sub": "erXuFYUfucBZaryVksYEcMg3"
}
So for anyone running into the same problem, so far I've found the answer is that you can't do it from the client side. This is an issue with the response returned from the server. If you don't have access to the server, you're SOL and will have to use a workaround like creating a proxy to send and accept all of your queries.
Specifically for my problem above, it was that the server did not include the following header
Access-Control-Expose-Headers:Location
Here are some other places to read about CORS that I found at least somewhat helpful:
Set cookies for cross origin requests
How to solve flutter web api cors error only with dart code?
https://github.com/flutterchina/dio/issues/1027
https://appvesto.medium.com/how-to-add-cors-to-the-dart-server-9d55a2835397

clientId missing from resource_access field in jwt token when using impersonation

I'm using Keycloak 14.0.0 and enabled the feature preview of token_exchange in order to do impersonation. After configuring my user in Keycloak to take on the impersonation role on the client "realm-management" (as according to the [documentation][1]), the actual request to do the token exchange fails as the token is not valid.
After some debugging it turns out that the jwt token is indeed malformed:
...
"session_state": "a03aeg0e-b5ce-4a50-9038-c339e50338c4",
"acr": "1",
"allowed-origins": [
"http://0.0.0.0:9180"
],
"scope": "openid identity_provider email admin profile company",
"permissions": [
"consented-readonly",
"readonly",
"trackingdisabled"
],
"resource_access": {
".roles": [
"impersonation"
]
},
"email_verified": false,
"idp": "myidp",
...
In the above, please notice the ".roles". I assume this is incorrect. It should be something like:
"resource_access": {
"myclient": {
"roles": [
"impersonation"
]
}
How can this be fixed?
[1]: https://www.keycloak.org/docs/latest/securing_apps/index.html#impersonation
It turns out that the configuration of a mapper was incorrect. In this case it was the "client roles" mapper (client scopes -> roles -> mapper -> client roles in keycloak ui) which, in my keycloak setup, had the value of:
resource_access..roles
This is incorrect as it should contain a clientId placeholder as shown below:
resource_access.${client_id}.roles
after this change the accessToken includes the actual client resulting in a valid json in the accessToken

Cloud Run Service requests always fail with 401 when using JWT returned for another service. The same request works with JWT returned for end-user

I have an app consisting of multiple Cloud Run Services. Each service is open only to a specific set of other services.
Let's say I have 2 services with URLs https://api-service-123.run.app and https://data-provider-service-123.run.app. The first service also has a custom URL https://api.my.domain.
api-service needs access to data-provider-service. So, following the documentation, I created two per-service user-managed service-accounts api-service-account#domain and data-provider-service-account#domain. I added api-service-account#domain into data-provider-service with Cloud Run Invoker role.
Now, I have trouble accessing the account with ID Token returned from Google. I tried several ways. Firstly, I utilized slightly adjusted code from the docks
export const getToken = async (url: string, targetAUD?: string) => {
const auth = new GoogleAuth();
const request = async () => {
if (!targetAUD) {
targetAUD = new URL(url).origin;
}
console.info(`request ${url} with target audience ${targetAUD}`);
const client = await auth.getIdTokenClient(targetAUD);
const res = await client.request({url});
console.info(res.data);
return res.data;
}
try {
return await request();
} catch (err) {
console.error(err);
}
}
When the function above is called as getToken('http://data-provider-service-123.run.app');, the request fails with 401 Unauthorized.
I also tried to call it as getToken('https://data-provider-service-123.run.app'); and got a response 403 Forbidden. The following entry is added to data-provider-service logs for each such request
{
httpRequest: {9}insertId: "60fcb7e0000c12346fe37579"
logName: "projects/my-project/logs/run.googleapis.com%2Frequests"
receiveTimestamp: "2021-07-25T01:01:20.806589957Z"
resource: {2}
severity: "WARNING"
textPayload: "The request was not authorized to invoke this service. Read more at
https://cloud.google.com/run/docs/securing/authenticating"
timestamp: "2021-07-25T01:01:20.800918Z"
trace: "projects/my-project/traces/25b3c13890aad01234829711d4e9f25"
}
I also tried to obtain and use JWT from compute metadata server (also a way advertised in the docs) by running curl "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=http://data-provider-servive-123.run.app" \ -H "Metadata-Flavor: Google".
When I execute this code in Cloud Shell, I get JWT that looks like this
{
"iss": "accounts.google.com",
"azp": "123456789-9r9s1c4alg36erliucho9t52n12n6abc.apps.googleusercontent.com",
"aud": "123456789-9r9s1c4alg36erliucho9t52n12n6abc.apps.googleusercontent.com",
"sub": "106907196154567890477",
"email": "my_email#gmail.com",
"email_verified": true,
"at_hash": "dQpctkQE2Sy1as1qv_w",
"iat": 1627173901,
"exp": 1627177501,
"jti": "448d660269d4c7816ae3zd45wrt89sb9f166452dce"
}
Then I make a request using postman to https://data-provider-service/get-data with a header Authorization: "Bearer <the_jwt_from_above>" and everything works fine
However, if I make the same request from my api-service, the JWT returned is
{
"aud": "http://data-provider-service-123.run.app",
"azp": "111866132465987679716",
"email": "api-service-account#domain",
"email_verified": true,
"exp": 1627179383,
"iat": 1627175783,
"iss": "https://accounts.google.com",
"sub": "111866132465987679716"
}
If I make the same request with the postman and place this JWT into the Authorization header, the 401 Unauthorized is returned.
I have spent this week trying to solve this issue. I triple-checked the permissions, redeployed the services several times, but nothing helps.
I changed http to https in the URL of the target service when asking computing metadata server, and looks like it solved the problem. Requesting token like this with google-auth-library still fails with 403 error.
I'll update that answer if the solution is sustainable.

PostGraphile "invalid algorithm" error with RS256 encrypted JWT toaken

I'm using JWT verification with PostGraphile 4.6.0 in a node project. Here is the the code snippet:
createServer(
postgraphile(env.DATABASE_URL, "public", {
jwtVerifyAlgorithms: ["RS256"],
jwtSecret: "./publickey.pem",
jwtPgTypeIdentifier: "public.jwt_token",
rejectUnauthorized: false,
graphiql: true,
enhanceGraphiql: true,
graphqlRoute: env.POSTGRAPHILE_ROUTE + "/graphql",
graphiqlRoute: env.POSTGRAPHILE_ROUTE + "/graphiql",
})).listen(port, () => {
console.log("Listening at port:" + port);});
But when I use Postman to send RS256 encrypted JWT token, get error:
{
"errors": [
{
"message": "invalid algorithm"
}
]
}
And I created a function in Postgres to return JWT token, it always return HS256 encrypted JWT token. And I I use PostGraphile returned HS256 encrypted JWT token in Postman, JWT token is validated and the GrqphQL query returns fine.
Appears that the option "jwtVerifyAlgorithms" doesn't take effect.
Is there a way to make this work for RS256 encrypted JWT token?
Set the sign algorithm in jwtSignOptions
The following configuration should work:
{
...
jwtPublicKey: fs.readFileSync(process.env.PUBLIC_KEY_FILE_PATH, 'ascii'),
jwtSecret: fs.readFileSync(process.env.SECRET_KEY_FILE_PATH, 'ascii'),
jwtSignOptions: { algorithm: 'RS256' },
jwtTokenIdentifier: 'foo.bar',
jwtVerifyAlgorithms: ['RS256'],
...
}
The Postgraphile documentation states jwtPublicKey as the correct setting for providing the keys. Right now you are using jwtSecret with the string "./publickey.pem" which will not load the file, but instead use the file path as a password.
Make sure to load the file yourself using something like this:
...
jwtPublicKey: fs.readFileSync("./publickey.pem", "ascii")
...
Alternative via pgSettings
Worst case scenario you can also provide a custom pgSettings function that uses the jsonwebtoken npm library and verify the token yourself and write it to your settings.

JWT token miss the claim in Nuxt Auth module with Auth0

In my Nuxt app after successful log in I have claim from Auth0. Devtools console shows it:
$vm0.$auth.$state.user['https://hasura.io/jwt/claims']
{x-hasura-default-role: "user", x-hasura-allowed-roles: Array(1), x-hasura-user-id: "auth0|5e989a*******"}
But my token doesn't have this claim, so my hasura requests fails. I check token right after claim in console:
$vm0.$auth.getToken('auth0')
"Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1ESTNNa0l5...
And I decode it using https://jwt.io/ and there is no claim:
{
"iss": "https://*******.eu.auth0.com/",
"sub": "auth0|5e989a*******",
"aud": [
"https://*******.eu.auth0.com/api/v2/",
"https://*******.eu.auth0.com/userinfo"
],
"iat": 1587059738,
"exp": 1587066938,
"azp": "MxJB0Y9dxvdGZnTAb2a4Y0YOHArvbkWt",
"scope": "openid profile email"
}
Here is my claim script in Auth0 dashboard:
function (user, context, callback) {
const namespace = "https://hasura.io/jwt/claims";
context.idToken[namespace] =
{
'x-hasura-default-role': 'user',
// do some custom logic to decide allowed roles
'x-hasura-allowed-roles': ['user'],
'x-hasura-user-id': user.user_id
};
callback(null, user, context);
}
I fixed this issue by changing claim script in Auth0 dashboard.
It should be context.accessToken[namespace] = {...} instead of context.idToken[namespace] = {...} and now I have that claim data inside my token.