Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heartbeat API Failing with 403 Error Post PubNub Access Manager Migration to v3 – Invalid Token Signature #431

Closed
vinaybongale opened this issue Jan 15, 2025 · 4 comments

Comments

@vinaybongale
Copy link

vinaybongale commented Jan 15, 2025

I am in the process of migrating from PubNub Access Manager v2 to v3 and encountering multiple issues related to token generation and validation. Below are the steps and changes I've made so far, along with the observed issues:


Changes Implemented:

Server-Side (Ruby):

  • Updated the token generation logic to use grant_token instead of grant.
  • Removed the auth_key as it is no longer required.
  • Here is the updated server-side token generation code:
def self.generate_token(user, entity_id)
  pubnub = Pubnub.new(next? ? private_config(user.uuid) : private_config)
  details = presence_details user
  permissions = {
    ttl: PRESENCE_TTL, # Token Time-to-Live in minutes (24 hours)
    authorized_uuid: user.uuid,
    channels: {
      "#{entity_id}#{PUBNUB_CHANNEL_DELIMETER}#{PUBNUB_WILDCARD}" => Pubnub::Permissions.res(
        read: true,
        write: true,
        manage: false
      )
    }
  }

  begin
    token_future = pubnub.grant_token(permissions)
    token_response = token_future.value
    if token_response.is_a?(Pubnub::Envelope) && token_response.status[:code] == 200
      token = token_response.result[:data]["token"]
      details.except(:auth_key).merge(token: token, user_id: user.uuid)
    else
      Rails.logger.info "Failed to generate token: #{token_response.status[:error]}"
    end
  rescue => e
    Rails.logger.info "Error: #{e.message}"
  end
end

Frontend Client (JavaScript/TypeScript):

  • Updated the frontend to use the newly provided token from the server.
  • Relevant client-side connection code:
async connect(options: any = {}) {
  const credentials = await this.options.resolveCredentials();

  if (!this.pubnub) {
    const config = this.options.resolveConfig(credentials, options);
    const { uuid: userId, publishKey, subscribeKey } = config;
    const v3PubNubConfig = { userId, publishKey, subscribeKey };
    this.pubnub = this.options.PubNubConstructor(v3PubNubConfig);
    this.pubnub.setToken(credentials.get('token', null));

    this.emitter = pubnubToEmitter(this.pubnub);

    this.pubnubPromise = promiseForEmitter(this.emitter);
    this.statusPromise = this.pubnubPromise(PubNubEvent.Status);
  } else {
    this.pubnub.setToken(credentials.get('token', null));
  }

  return credentials;
}
  • Additional Context:
    The connect method is a public method of the PubNubTransport class. Below is the constructor for the class where PubNubConstructor is defined which is used to above connection code to initialize PubNub:
export class PubNubTransport {
  constructor(opts: IPubNubTransportOptions) {
    this.options = fp.defaults(
      {
        ClientState: DefaultClientState,
        PubNubConstructor: (config) => new PubNub(config)
      },
      opts
    );

    this.listeners = Immutable.Map<string, Immutable.List<ListenerParameters>>();
  }
}

Observed Issues:

1. Heartbeat API Failure:

  • The Heartbeat API call fails with a 403 Forbidden error, as shown below:
{
  "error": true,
  "status": 403,
  "service": "Access Manager",
  "message": "Forbidden",
  "payload": {
    "channels": [
      "2136.proposal.UHJvcG9zYWw6WzE2NjcxNTksMTU1NDY3OV0=-pnpres",
      "2136.proposal.UHJvcG9zYWw6WzE2NjcxNTksMTU1NDY3OV0="
    ],
    "channel_groups": [],
    "users": [],
    "spaces": [],
    "uuids": []
  }
}
  • Heartbeat API call details:
http://ps19.pndsn.com/v2/subscribe/sub-c-xxxxxx/2136.proposal.UHJvcG9zYWw6WzE2NjcxNTksMTU1NDY3OV0%3D%2C2136.proposal.UHJvcG9zYWw6WzE2NjcxNTksMTU1NDY3OV0%3D-pnpres/0?heartbeat=300&uuid=f5f4bc78-0988-4375-887e-92883f97b1cd&pnsdk=PubNub-JS-Web%2F7.6.3&auth=qEF2AkF0GmeIDcFDdHRsGQWgQ3Jlc6VEY2hhbqFmMjEzNi4qA0NncnCgQ3NwY6BDdXNyoER1dWlkoENwYXSlRGNoYW6gQ2dycKBDc3BjoEN1c3KgRHV1aWSgRG1ldGGgRHV1aWR4JGY1ZjRiYzc4LTA5ODgtNDM3NS04ODdlLTkyODgzZjk3YjFjZENzaWdYIDWEOHvZrcTpiRX86DTc-yDuGL3HMgLgBuZnAoX1swZx
  • Despite migrating to v3, the v2 version of the subscribe API is being called.

2. Invalid Token Signature:

  • The token generated and returned by the backend fails signature validation on [jwt.io](https://jwt.io).
  • Below is an example token that produces the "Invalid Signature" error:
qEF2AkF0GmeGO7JDdHRsGQWgQ3Jlc6VEY2hhbqFmMjEzNi4qA0NncnCgQ3NwY6BDdXNyoER1dWlkoENwYXSlRGNoYW6gQ2dycKBDc3BjoEN1c3KgRHV1aWSgRG1ldGGgRHV1aWR4JGY1ZjRiYzc4LTA5ODgtNDM3NS04ODdlLTkyODgzZjk3YjFjZENzaWdYIDWEOHvZrcTpiRX86DTc-yDuGL3HMgLgBuZnAoX1swZx
Screenshot 2025-01-16 at 1 19 13 AM

Questions:

  1. Is there any specific configuration or step I might have missed during the migration from v2 to v3?
  2. Why is the Heartbeat API still calling the v2 endpoint even after migrating to v3 and updating the token management logic?
  3. Does the token generated in the grant_token API need any specific permissions for heartbeat to work correctly?
  4. Why does the generated token fail signature validation on jwt.io? Are there specific signing requirements that need to be handled differently for v3?

Thank you for your guidance! Let me know if you need more details.

@parfeon
Copy link
Contributor

parfeon commented Jan 15, 2025

@vinaybongale thank you for reaching out to us.

Warning

I've edited your message because one of the keys from the keyset has been leaked (if it is a test keyset - not a problem then).

Access token returned by server is not JWT, but CBOR encoded data which can be parsed for debug purpose with one of used languages.

JS has parseToken function which will parse provided token:

console.dir(pubnub.parseToken("qEF2AkF0GmeGO7JDdHRsGQWgQ3Jlc6VEY2hhbqFmMjEzNi4qA0NncnCgQ3NwY6BDdXNyoER1dWlkoENwYXSlRGNoYW6gQ2dycKBDc3BjoEN1c3KgRHV1aWSgRG1ldGGgRHV1aWR4JGY1ZjRiYzc4LTA5ODgtNDM3NS04ODdlLTkyODgzZjk3YjFjZENzaWdYIDWEOHvZrcTpiRX86DTc-yDuGL3HMgLgBuZnAoX1swZx"), {depth: 20});

Ruby has parse_token it:

puts @pubnub.parse_token("qEF2AkF0GmeGO7JDdHRsGQWgQ3Jlc6VEY2hhbqFmMjEzNi4qA0NncnCgQ3NwY6BDdXNyoER1dWlkoENwYXSlRGNoYW6gQ2dycKBDc3BjoEN1c3KgRHV1aWSgRG1ldGGgRHV1aWR4JGY1ZjRiYzc4LTA5ODgtNDM3NS04ODdlLTkyODgzZjk3YjFjZENzaWdYIDWEOHvZrcTpiRX86DTc-yDuGL3HMgLgBuZnAoX1swZx")

When parsed it looks like this:

{
  version: 2,
  timestamp: 1736850354,
  ttl: 1440,
  authorized_uuid: 'f5f4bc78-0988-4375-887e-92883f97b1cd',
  signature: Buffer(32) [Uint8Array] [
     53, 132,  56, 123, 217, 173, 196, 233,
    137,  21, 252, 232,  52, 220, 251,  32,
    238,  24, 189, 199,  50,   2, 224,   6,
    230, 103,   2, 133, 245, 179,   6, 113
  ],
  resources: {
    channels: {
      '2136.*': {
        read: true,
        write: true,
        manage: false,
        delete: false,
        get: false,
        update: false,
        join: false
      }
    }
  }
}

Which theoretically could match your need, but because RegEx used in resource section it treated as-is. To be able to use RegEx you NEED to use permissions for patterns as shown in this example.

@vinaybongale
Copy link
Author

vinaybongale commented Jan 16, 2025

Thank you for the suggestion to use pattern matching for permissions while generating the token and thanks for editing the message for leaked key. We implemented the recommended changes, and the token is now working as expected. Additionally, the heartbeat API issue has been resolved.

To provide more context, we are using PubNub features to show the icons of users visiting the same worksheet page on the worksheet UI. Based on this, I have a couple of follow-up questions:

  1. User Icons Visibility: While the heartbeat API is functional, we are still unable to see the icons of other logged-in users on the worksheet page. Could this be related to the permissions setup, or is there a specific configuration we might have missed for this use case?

  2. Heartbeat API Version: Although we’ve made changes for the PubNub Access Manager v3 migration, the heartbeat API is still calling the v2 version. Could you confirm if this is expected behavior or if additional steps are needed to fully migrate to v3?

Thank you for your guidance, and please let me know if you need any additional information to assist with these issues

@parfeon
Copy link
Contributor

parfeon commented Jan 17, 2025

@vinaybongale I'm glad to hear that permission issues have been resolved.

For followup questions:

  1. Since PubNub doesn't have any functionality for icons — this is implementation in your application. If it is presence of users, then you have to listen for presence listener using the listener interface on subscription object or global listener on PubNub instance.
  2. PAM and Presence APIs not related and each has their own versioning. Our servers understand what exactly they received with auth and process it correspondingly.

@vinaybongale
Copy link
Author

Thank you for the clarification and for addressing both of my questions.

I appreciate the confirmation that the behavior of the heartbeat API is expected and the explanation regarding the separation of PAM and Presence APIs. Additionally, I understand that the implementation of user icons needs to be handled on our side, and I will look into ensuring we correctly use presence listeners as suggested.

Thanks again for your support!

@parfeon parfeon closed this as completed Jan 17, 2025
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

No branches or pull requests

2 participants