api-platform icon indicating copy to clipboard operation
api-platform copied to clipboard

Mercure - automatic attribution of token for private updates

Open Jogima-cyber opened this issue 5 years ago • 19 comments

I'm using mercure with api-platform. Thing is, I want some ressources to be private on the mercure hub. But how does the client retrieve the token to access private topics ? Is there an API Platform mechanism to automatically retrieve the token for authorized users ?

If it's not implemented, I don't see the point of using API Platform with Mercure if I have to write a server-side token getter each time I want an update to be private. But That's most certainly because I'm new to API Platform and Mercure.

Jogima-cyber avatar Jun 12 '20 09:06 Jogima-cyber

Maybe I didn't get your question, but...

in the entity you need only mercure={"private": true},

In the login listener something like:

  $token = (new Builder())
        ->withClaim('mercure', ['subscribe' => $user->getMercureIri()])
        ->sign(new Sha256(), 'YOUR_SUPER_SECRET_KEY')
        ->getToken();

    $data['mercureToken'] = $token->__toString();

in in your User entity (which obviously matches the above)

public function getMercureIri(): array
{
    return ['api/users/'.$this->getId()];
}

Then when you connect to mercure it passes the token returned above. The part I didn't understand from docs was about the signing... which I got help from another coder on

On Fri, 12 Jun 2020 at 12:34, Jogima-cyber [email protected] wrote:

I'm using mercure with api-platform. Thing is, I want some ressources to be private on the mercure hub. But how does the client retrieve the token to access private topics ? Is there an API Platform mechanism to automatically retrieve the token for authorized users ?

If it's not implemented, I don't see the point of using API Platform with Mercure if I have to write a server-side token getter each time I want an update to be private. But That's most certainly because I'm new to API Platform and Mercure.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/api-platform/api-platform/issues/1559, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWLV7CS77MKNLDFLFSVF6TRWHZAXANCNFSM4N4FIWRA .

tpharaoh avatar Jun 12 '20 09:06 tpharaoh

If you are like me and you need a team path also, you can add that to your signing

On Fri, 12 Jun 2020 at 12:39, Tim S [email protected] wrote:

Maybe I didn't get your question, but...

in the entity you need only mercure={"private": true},

In the login listener something like:

  $token = (new Builder())
        ->withClaim('mercure', ['subscribe' => $user->getMercureIri()])
        ->sign(new Sha256(), 'YOUR_SUPER_SECRET_KEY')
        ->getToken();

    $data['mercureToken'] = $token->__toString();

in in your User entity (which obviously matches the above)

public function getMercureIri(): array
{
    return ['api/users/'.$this->getId()];
}

Then when you connect to mercure it passes the token returned above. The part I didn't understand from docs was about the signing... which I got help from another coder on

On Fri, 12 Jun 2020 at 12:34, Jogima-cyber [email protected] wrote:

I'm using mercure with api-platform. Thing is, I want some ressources to be private on the mercure hub. But how does the client retrieve the token to access private topics ? Is there an API Platform mechanism to automatically retrieve the token for authorized users ?

If it's not implemented, I don't see the point of using API Platform with Mercure if I have to write a server-side token getter each time I want an update to be private. But That's most certainly because I'm new to API Platform and Mercure.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/api-platform/api-platform/issues/1559, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWLV7CS77MKNLDFLFSVF6TRWHZAXANCNFSM4N4FIWRA .

tpharaoh avatar Jun 12 '20 09:06 tpharaoh

Hi @tpharaoh thanks for your answer. What you do is subscribe for private updates on user entity and you're doing it with a feature you've created. So, what if I wanted to do so with plenty other private entities, that are not the User entity ? I think I have to create for each entity a feature to retrieve the token to subscribe to the mercure private updates.

And indeed the signing example in the symfony doc isn't up to date.

Jogima-cyber avatar Jun 12 '20 09:06 Jogima-cyber

For me, it was just the signing part, I didn't get:

  $token = (new Builder())
        ->withClaim('mercure', ['subscribe' => $user->getMercureIri()])
        ->sign(new Sha256(), 'key')
        ->getToken();

    $data['mercureToken'] = $token->__toString();

But.. if each entity has ownerId the update will be send to the subscribed user

/**

  • @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="alarms")
  • @ORM\JoinColumn(nullable=false)
  • @Groups("alarm_read") */ private $owner;

So in the alarms example above, I have an owner column. And the mercure private=true and it knows to send to this user

On Fri, 12 Jun 2020 at 12:48, Jogima-cyber [email protected] wrote:

Hi @tpharaoh https://github.com/tpharaoh thanks for your answer. What you do is subscribe for private updates on user entity and you're doing it with a feature you've created. So, what if I wanted to do so with plenty other private entities, that are not the User entity ? I think I have to create for each entity a feature to retrieve the token to subscribe to the mercure private updates.

And indeed the signing example in the symfony doc isn't up to date.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/api-platform/api-platform/issues/1559#issuecomment-643182988, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWLV7GGZNAO65CIFOLYKBTRWH2YDANCNFSM4N4FIWRA .

tpharaoh avatar Jun 12 '20 09:06 tpharaoh

You mean, a user only needs one token to get all private updates related (with a ToMany/ManyTo/OneTo relation) to him ?

Jogima-cyber avatar Jun 12 '20 09:06 Jogima-cyber

https://api-platform.com/docs/core/mercure/#dispatching-private-updates-authorized-mode

this is in doc... but I just needed a bit more specific direction when I was building my first one

On Fri, 12 Jun 2020 at 12:51, Tim S [email protected] wrote:

For me, it was just the signing part, I didn't get:

  $token = (new Builder())
        ->withClaim('mercure', ['subscribe' => $user->getMercureIri()])
        ->sign(new Sha256(), 'key')
        ->getToken();

    $data['mercureToken'] = $token->__toString();

But.. if each entity has ownerId the update will be send to the subscribed user

/**

  • @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="alarms")
  • @ORM\JoinColumn(nullable=false)
  • @Groups("alarm_read") */ private $owner;

So in the alarms example above, I have an owner column. And the mercure private=true and it knows to send to this user

On Fri, 12 Jun 2020 at 12:48, Jogima-cyber [email protected] wrote:

Hi @tpharaoh https://github.com/tpharaoh thanks for your answer. What you do is subscribe for private updates on user entity and you're doing it with a feature you've created. So, what if I wanted to do so with plenty other private entities, that are not the User entity ? I think I have to create for each entity a feature to retrieve the token to subscribe to the mercure private updates.

And indeed the signing example in the symfony doc isn't up to date.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/api-platform/api-platform/issues/1559#issuecomment-643182988, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWLV7GGZNAO65CIFOLYKBTRWH2YDANCNFSM4N4FIWRA .

tpharaoh avatar Jun 12 '20 09:06 tpharaoh

The part when it says "the client must hold a JWT containing at least one target selector matched by the update." ?

Jogima-cyber avatar Jun 12 '20 09:06 Jogima-cyber

I think that's totally unclear ahaha. The work of @dunglas and other contributors is excellent and I thank them a great time for it, really that's awesome guys ! But just for this part in the doc, it would need a little bit more explanations.

Jogima-cyber avatar Jun 12 '20 09:06 Jogima-cyber

yes, based on the owner column

so, again in my example:

Automobile column has the same owner column as I wrote earlier and as does alarms. Then it can send to owner of auto and alarm entity.

The comment about subscripbing is in the client.

Mine is a VueJS mixin and I did as follows: mercureOpen(callback,channels) { const url = new URL(process.env.VUE_APP_MERCURE_URL+'/.well-known/mercure') url.searchParams.append('topic', process.env.VUE_APP_API_URL+ '/api/alarms/{id}') if(channels){ channels.forEach(channel => { url.searchParams.append('topic', process.env.VUE_APP_API_URL+'/api/'+channel +'/{id}') }); } this.es = new EventSourcePolyfill(url.toString(), { headers: { Authorization: 'Bearer ' + this.mercureToken } })

this.es.onmessage = e => callback(JSON.parse(e.data)) } },

The example here may not be the most eloquent, but I wanted alarms subscribed on each Vue page, and I feed the entity of that page or entities in an array when I call the mixin

There are far more qualified people here to answer... I just shared my challenges and solutions

On Fri, 12 Jun 2020 at 12:54, Jogima-cyber [email protected] wrote:

You mean, a user only needs one token to get all private updates related (with a ToMany/ManyTo/OneTo relation) to him ?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/api-platform/api-platform/issues/1559#issuecomment-643185457, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWLV7GXYBCJX22UNRPK7W3RWH3NXANCNFSM4N4FIWRA .

tpharaoh avatar Jun 12 '20 10:06 tpharaoh

@tpharaoh yes thanks a lot ! If it works like that it's totally awesome. I think a little more explanations in the doc would show to people like me who aren't so much qualified how powerful this feature is ! And have you tested if it works ? Can you really subscribe to private updates on related User's entities with only one token ?

Jogima-cyber avatar Jun 12 '20 10:06 Jogima-cyber

Well, no @dunglas did it right. Please note that part of the docs I referred to is for the API component, so its related to the symfony project, not to how you connect your front end

The client component docs here are for the tool they provided. If you do not use their tool (and I don't) you need to follow the mercure.rocks doc... and it says it clearly in the getting started

https://mercure.rocks/docs/getting-started

const url = new URL('http://localhost:3000/.well-known/mercure'); url.searchParams.append('topic', 'https://example.com/books/{id}'); url.searchParams.append('topic', 'https://example.com/users/dunglas');

In an authenticated scenario, it will require your token to get those updates

I also didn't find all I needed the first times. To be honest, I was a bit too impatient. So, I got someone to make me a 1 page example. After I read the code, I went back and found it in docs. Now though there are far more examples on github or tutorials online than a few years back.

On Fri, 12 Jun 2020 at 12:59, Jogima-cyber [email protected] wrote:

I think that's totally unclear ahaha. The work of @dunglas https://github.com/dunglas and other contributors is excellent and I thank them a great time for it, really that's awesome guys ! But just for this part in the doc, it would need a little bit more explanations.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/api-platform/api-platform/issues/1559#issuecomment-643187372, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWLV7GV5CM6VQGXC36XU4DRWH36JANCNFSM4N4FIWRA .

tpharaoh avatar Jun 12 '20 10:06 tpharaoh

Yes, I have multiple projects live using mercure for mobile apps and TV dashboards.

The docs are fine. But, if you don't use ALL their tools, you need to look in more than one place. I don't think thats a fault of the docs. IMHO its just they can't cover each niche case. For my needs, I need docs from 3 places. But, I am also a bit niche.

It is FAR easier than using websockets. An older project I made integrated laravel-websockets with Symfony and the loops I had to run through for the out of box solutions here was much more.

If you think about it in this way..

you sign with your allowed paths, you return a token.

That token goes in header when subscribing to mercure

and ANY one of those paths should be added in the topic when you subscribe. That as simple as I can put it.

On Fri, 12 Jun 2020 at 13:05, Jogima-cyber [email protected] wrote:

@tpharaoh https://github.com/tpharaoh yes thanks a lot ! If it works like that it's totally awesome. I think a little more explanations in the doc would show to people like me who aren't so much qualified how powerful this feature is ! And have you tested if it works ? Can you really subscribe to private updates on related User's entities with only one token ?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/api-platform/api-platform/issues/1559#issuecomment-643190304, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWLV7GK5HL3CHBCJPIOHXLRWH4YBANCNFSM4N4FIWRA .

tpharaoh avatar Jun 12 '20 10:06 tpharaoh

I must admit I'm a little bit too impatient too, my bad. Okay I see, and the mercure hub knows if a private update is related to a user, because api-plaform sends to the mercure hub related users on an entity when the entity is updated.

Jogima-cyber avatar Jun 12 '20 10:06 Jogima-cyber

Okay, thanks a lot @tpharaoh for your help. I was trying to find help on stackoverflow and on the api-platform on slack, but nobody answered me.

Jogima-cyber avatar Jun 12 '20 10:06 Jogima-cyber

@tpharaoh is there a way to reward help on github ? I'm entirely new to github ahah.

Jogima-cyber avatar Jun 12 '20 10:06 Jogima-cyber

Well... I didn't see a sponsor link for the ApiPlatofrm team, you can hit up @dunglas on twitter to see if they have a github sponsor link.

BTW, if you want to see a project I did with a guy on upwork, you can see this. Its outdated, old mercure and old vue but it might give you a hint (note the private=true method it not used here, so you can't use this as it in a copy/paste) https://github.com/tpharaoh/apiplatformtemplate

On Fri, 12 Jun 2020 at 13:15, Jogima-cyber [email protected] wrote:

@tpharaoh https://github.com/tpharaoh is there a way to reward help on github ? I'm entirely new to github ahah.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/api-platform/api-platform/issues/1559#issuecomment-643194353, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAWLV7GVARPAE2CPUHJYNGTRWH55NANCNFSM4N4FIWRA .

tpharaoh avatar Jun 12 '20 10:06 tpharaoh

Hi there, I've tried what you told me, but it doesn't seem to work. From server, the token builder part.

  $mercure_token = (new Builder())
        ->set('mercure', ['subscribe' => ["/api/users/".strval($user->getId())]])
        ->sign(new Sha256(), 'SECRET_KEY')
        ->getToken();

Client (React-Native) then subscribe using this token to mercure to topic /api/email_tokens/{id} where {id} must be replaced with the id number :

mercureSubscribe(
   new URL(hubURL),
   '/api/email_tokens/'+email_token_id,
   mercure_token
)
export function mercureSubscribe(hubURL, topic, mercure_token) {
  return dispatch => {
    const eventSource = subscribe(hubURL, [topic], mercure_token);
    dispatch(mercureOpen(eventSource));
    eventSource.addEventListener('message', event =>
      dispatch(mercureMessage(normalize(JSON.parse(event.data))))
    );
  };
}

export function subscribe(url, topics, token) {
  topics.forEach(topic =>
    url.searchParams.append('topic', new URL(topic, ENTRYPOINT))
  );
  var options = {}
  if(token) options = { headers: { Authorization: 'Bearer '+token } };

  return new EventSource(url.toString(), options);
}

The EmailToken entity :

[...]
/**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="emailTokens")
     * @ORM\JoinColumn(nullable=false)
     */
    private $user;
[...]

Then I got this from mercure hub : Capture d’écran 2020-07-22 à 18 44 33

Jogima-cyber avatar Jul 24 '20 09:07 Jogima-cyber

I don't think the native EventSource supports Authorization header, that's why it's recommended to use a polyfill

pourquoi avatar Dec 13 '21 16:12 pourquoi

generate jwt token using mercure Hub register


    public function __construct(HubRegistry $hub)
    {
        $this->generator = $generator;
    }
      
   public function  __invoke () {
    $topics = []

    $token = $this->hub->getHub()->getFactory()->create($topics);
  }

SakhriHoussem avatar Jun 29 '24 18:06 SakhriHoussem