Published on

Auth0 Unauthorized JWT Malformed Error

Authors

Auth0 is an awesome tool. But one issue we ran into when using it was that the configuration you need to use from the UI and server is not clear in the tutorials. They speak about ${authConfig.domain} which is not 100% clear.

In my case, I ran into the following error when trying to hit an authorized endpoint on the express side: UnauthorizedError: jwt malformed.

My (wrong) configuration on the UI looked as below:

import auth0 from 'auth0-js'

//...
this.auth0 = new auth0.WebAuth({
  domain: 'my-test-app-foo-bar.au.auth0.com',
  audience: 'https://my-test-app-foo-bar.au.auth0.com/userinfo',
  clientID: 'AAAAAAAAAAAAAAAAAAAAAAAAlNbfTQGT',
  redirectUri: 'http://127.0.0.1:3000/callback',
  responseType: 'code token id_token',
  scope: 'openid profile email',
})

The backend uses express with the Auto0 middleware. The (wrong) configuration for that looked like this:

var jwt = require('express-jwt')
var jwksRsa = require('jwks-rsa')

var checkJwt = jwt({
  // Dynamically provide a signing key
  // based on the kid in the header and
  // the signing keys provided by the JWKS endpoint.
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: 'http://127.0.0.1:3002/.well-known/jwks.json',
  }),

  // Validate the audience and the issuer.
  audience: 'https://my-test-app-foo-bar.au.auth0.com/api/v2/',
  issuer: 'http://127.0.0.1:3002/',
  algorithms: ['RS256'],
})

module.exports = checkJwt

After a bit of Googling my colleague and I came across this post.

Based on this my wrong configuration for the UI was changed to:

import auth0 from 'auth0-js'

//...
this.auth0 = new auth0.WebAuth({
  domain: 'my-test-app-foo-bar.au.auth0.com',
  audience: 'https://my-test-app-foo-bar.au.auth0.com/api/v2/', //the end bit was changed
  issuer: 'https://my-test-app-foo-bar.au.auth0.com', //added
  clientID: 'AAAAAAAAAAAAAAAAAAAAAAAAlNbfTQGT',
  redirectUri: 'http://127.0.0.1:3000/callback',
  responseType: 'code token id_token',
  scope: 'openid profile email',
})

The server portion was changed to:

var jwt = require('express-jwt')
var jwksRsa = require('jwks-rsa')

var checkJwt = jwt({
  // Dynamically provide a signing key
  // based on the kid in the header and
  // the signing keys provided by the JWKS endpoint.
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: 'https://my-test-app-foo-bar.au.auth0.com/.well-known/jwks.json',
  }),

  // Validate the audience and the issuer.
  audience: 'https://my-test-app-foo-bar.au.auth0.com/api/v2/',
  algorithms: ['RS256'],
})

module.exports = checkJwt

When you login to Auth0 and have everything configured properly you should see a response similar to the below (this is an example where Google was used to login - some data has been changed for privacy but the length of the strings is accurate):

{
  "accessToken": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.cccccccccccccccccccccccccccccccccccccc-ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd-_eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
  "idToken": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc-ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd-eeee-fffffffffffffffffffffffffffffffffffffff-gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg-hhhh-iiiiiiiiiiiii",
  "idTokenPayload": {
    "given_name": "John",
    "family_name": "Smith",
    "nickname": "johnsmith",
    "name": "John Smith",
    "picture": "https://lh5.googleusercontent.com/-k-AAAAAAAAA/AAAAAAAAAAI/AAAAAAAAAAA/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB/photo.jpg",
    "locale": "en-GB",
    "updated_at": "2019-09-09T14:56:48.889Z",
    "email": "[email protected]",
    "email_verified": true,
    "iss": "https://my-test-app-foo-bar.au.auth0.com/",
    "sub": "google-oauth2|777777777777777777777",
    "aud": "TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT",
    "iat": 1568041008,
    "exp": 1568077008,
    "at_hash": "AAAAAAAAA_BBBBBBBBBBBB",
    "c_hash": "CCCCCCCCCCCCCCCCCCCCCC",
    "nonce": "AAAAA-BB.CCCCCCCCCCCCCCCCCCCCCCC"
  },
  "appState": null,
  "refreshToken": null,
  "state": "AAAAAA.BBCC.DDDDDDDDDDDDDDDDDDD-",
  "expiresIn": 7200,
  "tokenType": "Bearer",
  "scope": null
}

Any requests that get sent to the server that needs to be authenticated should include a header with accessToken from above as the bearer as in the below example that uses Axios:

var instance = axios.create({
  baseURL: 'http://127.0.0.1:3002/',
  timeout: 1000,
  headers: { Authorization: `Bearer ${accessToken}` },
})

When using this with the Auth0 middleware above and using the checkJwt method, if all is good the express req object is enriched with a user object which looks like below:

{ iss: 'https://my-test-app-foo-bar.au.auth0.com/',
  sub: 'google-oauth2|777777777777777777777',
  aud:
   [ 'https://my-test-app-foo-bar.au.auth0.com/api/v2/',
     'https://my-test-app-foo-bar.au.auth0.com/userinfo' ],
  iat: 1568041885,
  exp: 1568049085,
  azp: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
  scope: 'openid profile email' }

In the above a few things to note:

  • .au. is because the region we chose for Auth0 is Au -> Australia. Yours will differ/be the same depending on the region you chose.
  • A telltale sign that your application token is wrong is if it is too short - ours was about 30 characters instead of a much much bigger string (as can be seen from the example response further above).
  • You can use sub to uniquely identify a user in your database. The user can revoke/invalidate this at any time should they wish.