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

Claiming podcasts - what can we do to help? #342

Closed
jamescridland opened this issue Feb 18, 2022 · 20 comments
Closed

Claiming podcasts - what can we do to help? #342

jamescridland opened this issue Feb 18, 2022 · 20 comments
Labels
proposal An idea for a new tag

Comments

@jamescridland
Copy link
Contributor

jamescridland commented Feb 18, 2022

Use case

Currently, if you want to "claim" a podcast on most podcast directories, then they achieve this in one of two ways.

  1. Publisher types in their RSS feed into a podcast directory
  2. Podcast directory reads the email in the RSS feed
  3. Podcast directory sends a TOTP token or a URL via email
  4. Owner of the podcast opens their email and finds the mail from the Podcast Directory
  5. Owner of the podcast clicks that link.
  6. Now, the podcast directory reasonably knows that the publisher is the owner of the podcast - at least, the owner of the email address.

This falls over in a few ways:
a. it relies on quick transmission of emails, unimpeded by spam filters
b. the podcast publisher needs access to this email address (quite hard for a large team)
c. the email address could be used for multiple podcasts
d. it causes a lot of basket abandonment, as podcasters have to move from a website to an email, get distracted by the other emails there, and go and do something else, forgetting to claim their podcast

The drawbacks with 'claiming' feeds have caused some podcast apps/directories to just not bother with this step, and that's a mistake that would be nice to fix, too.

Suggestion

I don't really know what I'm doing here, but I'd like to suggest some form of key/token thing that allows this to be automated. So the above is changed to...

  1. Publisher types their RSS feed into a podcast directory
  2. The podcast directory redirects you to your podcast publisher
  3. The publisher has a simple page (behind your login) which says "Do you want to claim your podcast on XX?"
  4. You click "yes" and it takes you back to the podcast directory, which now knows you've claimed your podcast.

This seems quite similar to OAuth, so there might be something there in how it works. I think we also need to give an auth URL for a podcast publisher. This would a) tell the podcast directory that this is supported; and b) tell the podcast directory where to go to request clarification from the podcast host that this really is this person's podcast.

<podcast:locked auth="https://example.com/claiming/login.php" owner="email@example.com">yes</podcast:locked>

This is a problem, and it would be really nice to have a programmatic, smooth solution to it. I'm not sure I've identified a solution yet.

Later: I've added some documentation below as to how I think it might work.

@brianoflondon
Copy link
Contributor

brianoflondon commented Feb 18, 2022

I can see how to do this.

A simple api endpoint at the podcast host listed in the feed that accepts a token and something to identify the podcast and a return api hook to send the response to (which probably has the token in just like the link in the email.

Space for an optional identifying message from the service to the podcaster is probably a good idea too.

Podcast host has a notification for users in their dashboard (presumably they know they're signing up for something so they're watching for this).

One click and the token is sent back to the requester. Job done no email.

I believe I could build this for 3speak as a demo very quickly.

One issue... The services love harvesting emails. But this would be much better.

  • And it doesn't even use the blockchain 😂😂😂😂😂

@jamescridland
Copy link
Contributor Author

jamescridland commented Feb 18, 2022

I think the OAuth-type of bouncing from PodcastDirectory to PodcastHost works. No need for a notification system. But you're absolutely right - a token was what I was looking for!

directory.example.com looks at the RSS feed, sees the auth value, and says...

Your podcast host supports quick claiming. [Click here to claim your podcast]

... linking to the 'auth' URL, and posting to it "token=3c2a2f4e&rss=https://host.example.com/rss&callback=https://directory.example.com/callback"

host.example.com takes a user through authentication if not logged in, and shows...

Hi, Brian.
SuperPods wants to let you claim the Five Eyes Podcast on their service.
Is that OK?
[x] Share my email address with them too
[CLAIM MY SHOW ON SUPERPODS] [Cancel]

...and then posts to https://directory.example.com/callback the token and RSS.

I think OAuth has some additional security on this.

@nathangathright
Copy link
Contributor

nathangathright commented Feb 18, 2022

At pod.link, we let podcasters claim their show with the email method. It definitely has its downsides, especially with Anchor and Acast shows, and I've been imagining how a better solution might work.

To make it more familiar to users, I'd mimic the language of other oAuth flows. So the app could see the auth value and just show a Single Sign-On button like: Sign in with $host_name (without explaining what "quick claiming" is), and the host could show a screen like:

Authorize $app_name to confirm your access to $show_name?
[Authorize app] [Cancel]

or maybe

Verify access to $show_name on $app_name?
[Verify] [Cancel]

Any participating service would still have to scrape the email address as a fallback for non-participating hosts. So the checkbox to share an email address seems redundant to me.

@daveajones
Copy link
Contributor

Would definitely want to get hosting company input on these ideas. @tomrossi7, @albertobeta, @amandato, @PofMagicfingers, @mkadin ?

@jamescridland
Copy link
Contributor Author

Agreed! Would love host feedback, and will be pushing this on Monday.

I'd also be happy to write a simple PHP example codebase for this (the benefit of that being that it can be written simply enough to be clear how it works for hosts to implement).

@daveajones
Copy link
Contributor

Good idea James. Real code always helps.

@jamescridland
Copy link
Contributor Author

jamescridland commented Feb 21, 2022

How does this vaguely work for documentation? Rewritten slightly to be a separate tag.

"Claiming" a podcast in a podcast directory means that your customer can be authorised as the owner of that podcast in a directory. You might claim a podcast in Podchaser to add host information; in GoodPods to be notified of comments; or in Spotify to add it to the Spotify dashboard for analytics.

A "quick-claim" is a programmatic way to claim a podcast, by taking your customer from a podcast directory to an authenticated page on their podcast host.

For podcast directories

Run a podcast directory, and want to check that someone owns this podcast?

Benefits of using "quick-claim" are that it's one-click within a browser or webview, rather than sending a potential user an email. Assuming they are logged-in to their podcast host (or can log in), they can approve your request instantly and come back to your website within seconds, thus significantly lowering your abandoned shopping cart or registration. Additional benefits are that anyone authorised to access the podcast host dashboard may claim the show; and the email address within the RSS feed need not be the one used by your user.

Check that quick-claiming is enabled in the RSS feed.

<podcast:verify auth="https://example.com/claiming/login.php"></podcast:verify>

As above, you should see an auth field within the <podcast:locked> tag. If you do, this podcast is quick-claim enabled, and you can give them this quick procedure.

Give them a "claim this with your podcast host" button

You will send a POST to the auth field present in the podcaster's RSS feed, with three values:

a. guid - A podcast:guid is either present in the RSS feed already, or can be calculated from its original RSS feed address. This is the UUIDv5 GUID for a podcast feed, not the individual episode guid. Here's the spec for that.
b. token - this can be whatever you want it to be. You will see it coming back from the podcast host when the podcast has been verified. You may include in it some form of userID and security token.
c. auth is the auth URL in the RSS feed.

The returnurl field below is where you would like to be sent this information back if the user agrees.

Here's an example in PHP of your button:

echo '<form method="post" action="'.$auth.'">';
echo '<input type="hidden" name="guid" value="'.$guid.'">';
echo '<input type="hidden" name="token" value="'.$token.'">';
echo '<input type="hidden" name="returnurl" value="https://directory.example.com/claim/process">';
echo '<input type="submit" value="Claim this now">';
echo '</form>';

Handle the response

If the user wants to claim the podcast, they will press a button on their podcast host's website, and you will get a POST to the returnurl above. It will contain the same GUID as above, and the token.

Check the GUID is the one you were expecting; and ensure the token is correctly formatted. If it is, the user has successfully claimed the podcast.

For podcast hosts

Quick-claim is designed to allow your customers to demonstrate that they own their podcast on third-party services, like Podchaser or Goodpods, as an example.

The benefits of using "quick-claim" are that it's one-click for your customers from the service they wish to claim, direct to you. You can monitor the services your users are using, and can give multiple people access to claiming a podcast on a separate service based on your service's access levels.

Ease of use on third-party services retains the customer with your company, thus lowering churn, and possibly giving them more access and interaction, prolonging their activity with you.

If you're a podcast host wanting to add quick claiming for your customers, then here's how you can do it painlessly:

Add a podcast:verify field in your RSS feeds

The field needs two values:
a. auth - a URL to a page that is protected on your server by customer login. It is highly recommended that this be an https address.

Example:
<podcast:verify auth="https://amazingpodcasthost.example.com/claiming/login.php"></podcast:verify>

Write a page for a user to agree to "claim" a podcast

The podcast directory will send a POST request to this URL, containing three values: token, guid and returnurl. You should return the token and guid to the returnurl that they have sent.

A podcast:guid is either present in your RSS feed already, or can be calculated from its original RSS feed address. This is the UUIDv5 GUID for a podcast feed, not the individual episode guid. Here's the spec for that.

Here's an example in PHP:

if (!$user['loggedin']) {
   // this user is not logged in. Take them to log in
   // Retain the token, guid and returnurl, and bring them back here
   exit;
}

// You might want to give confirmation that the podcast they are claiming
// is the correct podcast.
// Grab the podcast details in an array from your local systems.
// Check that the podcast is owned by this person, of course.
$podcast = lookup_from_guid($_POST['guid']);

echo '<h1>Claiming your podcast</h1>';
echo '<p>The website at '.parse_url($_POST['returnurl'],PHP_URL_HOST);
echo ' wants to check you own '.$podcast['name'];
echo '. Click the button below to show that you do.</p>';
echo '<form method="post" action="'.$_POST['returnurl'].'">';
echo '<input type="hidden" name="guid" value="'.$podcast['guid'].'">';
echo '<input type="hidden" name="token" value="'.$_POST['token'].'">';
echo '<input type="submit" value="I do">';
echo '</form>';

The above will successfully check that your user is authenticated and send back the token to the directory service if the user agrees.

@brianoflondon
Copy link
Contributor

I know it is a somewhat far off dream... but I'd dearly love to dump having to put emails in feeds completely. It's just a nasty spam harvesting system as it is today. This would certainly be the first step on that journey.

@jamescridland
Copy link
Contributor Author

jamescridland commented Feb 21, 2022

Agree. To be honest, we don't need them for this; it's just part of the "other" use of the podcast:locked tag. I wonder if I can remove them completely from this documentation, and just pass comment that they're also used for transfer lock.

Or, and perhaps better, we use the same auth for transfer locking.

@PofMagicfingers
Copy link
Contributor

PofMagicfingers commented Feb 21, 2022

Hi,

I really like this idea of quick claiming.

Quick idea here : I'm not 100% sure it fits with "podcast locking" term. If we complexify a bit the usage maybe we could rename the tag to something like podcast:guard :
<podcast:guard locked="yes|no" auth="[url]"/>

But most importantly, as I see the value behind this idea, the implementation we're currently discussing has some flaws IMHO.

First, it doesn't seem enough secure to me, as there is no way to authenticate the server response :

If we visit a directory with a claim button these are public informations : guid, claiming auth_url are in the feed, the token the directory is generating, and the return_url.
If it's not clearly in the source code, inspected page, it will be visible in the inspector network tab somehow at some point.

With the current implementation of the draft protocol, anyone can claim any feeds without having to log into the hosting provider.
The requirement for claiming is : to do a POST request to return_url with the directory token and podcast guid. All theses informations are public. That POST request could come from anyone authenticated or not.
Anyone could make a chrome extension scraping the data in the directory and firing POST requests claiming all feeds.

The second issue I see is "user path/worklow". What happens if the user can't login, or click a back link, or something has failed (e.g. the user has no access to this podcast) ? We need to mimick what payment platform like paypal uses : success_url, error_url, back_url. That's a bit verbose, but don't worry we can simplify, i have a few ideas.

Third issue is that we have no way to "identify" the service asking for permission. What works with OAuth is that apps are registered into the service provider, so it can show the user a name, icon and purpose. If we wan't to keep a decentralized approch, we need to add these informations somewhere.

I'll use the great doc wrote by @jamescridland and use it as a base to explain how I would implement this, as a podcast directory & podcast hosting company. I'll use the podcast:guard new name because I like it, but it works any way.


"Claiming" a podcast in a podcast directory means that your customer can be authorized as the owner of that podcast in a directory. You might claim a podcast in Podchaser to add host information; in GoodPods to be notified of comments; or in Spotify to add it to the Spotify dashboard for analytics.

A "quick-claim" is a programmatic way to claim a podcast, by taking your customer from a podcast directory to an authenticated page on their podcast host.

For podcast directories

Run a podcast directory, and want to check that someone owns this podcast?

Benefits of using "quick-claim" are that it's one-click within a browser or webview, rather than sending a potential user an email. Assuming they are logged-in to their podcast host (or can log in), they can approve your request instantly and come back to your website within seconds, thus significantly lowering your abandoned shopping cart or registration. Additional benefits are that anyone authorised to access the podcast host dashboard may claim the show; and the email address within the RSS feed need not be the one used by your user.

Check that quick-claiming is enabled in the RSS feed.

<podcast:guard locked="yes" owner="some@email.com" auth="https://hostingprovider.com/claiming/" pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9 q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg=="/>

As above, you should see an auth field within the <podcast:guard> tag, this means the feed support quick claiming. The pub attribute is a public key you will use later to check the autenticity of the hosting provider response.

Give them a "claim this with your podcast host" button

You will redirect your user to the auth url present in the podcaster's RSS feed, with 1 to 3 URL parameters :

consumer

Your directory/service base URL of the return_path.

It's recommanded to support open graph allowing the hosting provider to present a clean name and icon for your service while asking permission. If open graph is not supported, hosting provider will most likely fallback to hostname, path, favicon.

Splitting the return url into a consumer and a return_path gives us control of which part of the url is used for open graph data while ensuring the return path is tied to the hostname and identity presented to the user.

Examples:

consumer=https://podcastindex.org

PodcastIndex.org (podcastindex.org) 
The Podcast Index is here to preserve, protect and extend the open, independent podcasting ecosystem.

This service would like to verify you are the owner of this podcast. Do you want us to confirm ?

consumer=https://podcastindex.org/quick_claim

🗼PodcastIndex Quick Claiming (podcastindex.org) 
Claiming your podcast into The Podcast Index directory gives you cool stuff !

This service would like to verify you are the owner of this podcast. Do you want us to confirm ?

return_path

A path relative to consumer parameter, to redirect with the result of the authentication.
If the consumer is enough, you can omit it.

Examples:

consumer=https://podcastindex.org/quick_claim (no return path)
➡️ full return url will be https://podcastindex.org/quick_claim?token=[token]

consumer=https://podcastindex.org/quick_claim&return_path=/claimed.php
➡️ full return url will be https://podcastindex.org/quick_claim/claimed.php?token=[token]

consumer=https://podcastindex.org/quick_claim&return_path=../claim.php
➡️ full return url will be https://podcastindex.org/claim.php?token=[token]

guid

If a podcast:guid is present in the RSS feed it SHOULD be included. If it's not we omit it or send an empty string.

We keep it simple, we should trust the users of our spec (in this case the hosting providers) : if they don't include a podcast:guid, I don't see the point to calculate it and give it to them, as they most likely don't have it on their hand and probably don't use it. If they would, it would be included in the feed.

If we don't have any podcast:guid, maybe the hosting provider has only partial support of the podcasting 2.0 spec, maybe they only support claiming and include the guid in the auth url, maybe it's a wordpress extension and there is only one podcast and no need to "select a podcast" when you're logged in. We can't know and guess all usages.

If we really want them to have a podcast:guid to use quick claiming, why not adding quick claiming into podcast:guid ?

<podcast:guid auth="https://hostingprovider.com/claiming/" pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9 q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==">[here the podcast guid]</podcast:guid>

It kind of fits, as it's the feed identity control.

PHP Example

Here's an example in PHP of your button:

<a 
   class="btn btn-default"
   href="<?php echo $feed_auth_url."?consumer=https://myservice.com/quick_claim&guid=".$feed_guid; ?>">
   Claim this now
</a>

Handle the response

If the user wants to claim the podcast, they will confirm it inside their hosting service. You will get a GET request to the return URL computed with the consumer and return_path.

The GET request will contain only one parameter : token.

This parameter will be a JSON Web Token including the following data :

type QuickClaimResponse = {
	guid?: GUID;
	accepted: true;
} | {
	guid?: GUID;
	accepted: false;
	failureReason: "back" | string;
};

Using JWT let us ensure the hosting provider wrote the response. It also allow us to define or not an expiration date and such. (More info)

The JWT must use an asymetric signing algorithm (ES256 for example). The response is signed by the hosting provider private key, and it can be verified using the pub attribute of the tag (put there by the same hosting provider).

You can verify the signature in PHP with a JWT library, if it fails to decode, it means the signature is wrong or the token has expired:

$result = JWT::decode($quickClaimResponse, $feed["podcast:guard"]["pub"]);

As a podcast directory/service you have now the confirmation the user was indeed authenticated, confirmed the operation, and that its the same entity giving you this information and producing the RSS feed.

For podcast hosts

Quick-claim is designed to allow your customers to demonstrate that they own their podcast on third-party services, like Podchaser or Goodpods, as an example.

The benefits of using "quick-claim" are that it's one-click for your customers from the service they wish to claim, direct to you. You can monitor the services your users are using, and can give multiple people access to claiming a podcast on a separate service based on your service's access levels.

The email in your RSS feed need not be the registration email of the user on this third-party service, thus lowering your customer support calls and streamlining access for your customers. Ease of use on third-party services retains the customer with your company, thus lowering churn, and possibly giving them more access and interaction, prolonging their activity with you.

If you're a podcast host wanting to add quick claiming for your customers, then here's how you can do it painlessly:

Add a podcast:guard tag in your RSS feeds

The tag needs two attributes :

a. auth - a URL to a page that is protected on your server by customer login. It is highly recommended that this be an https address.
b. pub - the public key corresponding to the private key you'll use to sign claiming requests.

Example: <podcast:guard auth="https://amazingpodcasthost.example.com/claiming" pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9 q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==" />

Host a page for a user to agree to "claim" a podcast

Your claiming auth url will be called with 1 to 3 parameters :

consumer

The directory/service base URL.

This url must be used to present the permission asker to the user. It's recommanded for services to support open graph on this URL allowing the hosting provider to present a clean name and icon. If open graph is not supported, you will most likely fallback to title and favicon, hostname only, or full url.

Splitting the return url into a consumer and a return_path gives us control of which part of the url is used for open graph data while ensuring the return path is tied to the hostname and identity presented to the user.

return_path

A path relative to consumer parameter, to redirect with the result of the authentication.
If the consumer is enough, you can omit it.

Examples:

consumer=https://podcastindex.org/quick_claim (no return path)
➡️ full return url will be https://podcastindex.org/quick_claim?token=[token]

consumer=https://podcastindex.org/quick_claim&return_path=/claimed.php
➡️ full return url will be https://podcastindex.org/quick_claim/claimed.php?token=[token]

consumer=https://podcastindex.org/quick_claim&return_path=../claim.php
➡️ full return url will be https://podcastindex.org/claim.php?token=[token]

guid

If a podcast:guid was present in the RSS feed.

Here's an example in PHP:

<?php
$HOSTING_PRIVATE_KEY = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G";

if (!$user['loggedin']) {
   // this user is not logged in. Take them to log in
   // Retain the consumer, return_path, guid, and bring them back here
   //
   exit;
}

// You might want to give confirmation that the podcast they are claiming
// is the correct podcast.
// Grab the podcast details in an array from your local systems.
// Check that the podcast is owned by this person, of course.

$consumer = $params["consumer"];
$return_path = $params["return_path"];
$podcast = lookup_from_guid($params['guid']);

// If podcast is not found on this user we redirect with an error message:

if(!$podcast) {
     header(
   	    "Location: ".
   	    $consumer.
   	    $return_path.
   	    "?token=".
   	    build_jwt_token(
   		      [
   			      "accepted" => false,
   			      "error" => "Podcast could not be found for this user"
   		      ], 
   		      $HOSTING_PRIVATE_KEY
   	    )
     );
     exit();
}

$guid = $podcast["guid"];


// If we already shown the page and got user response 
// 
// checking csrf depends on your context, but is a strong recommendation for security
if(check_csrf()) exit("Bad request");

$action = $params["action"];
if($action == "accept") {
   header(
   	"Location: ".
   	$consumer.
   	$return_path.
   	"?token=".
   	build_jwt_token(
   		[
   			"accepted" => true
   		], 
   		$HOSTING_PRIVATE_KEY
   	)
   );
   exit();
} else if ($action == "back") {
   header(
   	"Location: ".
   	$consumer.
   	$return_path.
   	"?token=".
   	build_jwt_token(
   		[
   			"accepted" => false,
   			"error" => "back"
   		], 
   		$HOSTING_PRIVATE_KEY
   	)
   );
   exit();
}

$service = opengraph($consumer); 

$claim_fields = <<<TAGS
<input type="hidden" name="guid" value="$guid" />
<input type="hidden" name="consumer" value="$consumer" />
<input type="hidden" name="return_path" value="$return_path />
TAGS;

?>
<h1>Claiming your podcast</h1>
<img src="<?= $service["icon"] ?>">
<h3>
   <?= $service["name"] ?>
   <small>(<?= parse_url($consumer,PHP_URL_HOST) ?>)</small>
</h3>
<p>This service wants us to confirm you have control over this podcast : <?= $podcast["name"]; ?></p>
<form method="post" action="/quick_claim">
<?= $claim_fields ?>
<?= put_csrf_protection() ?>
<input type="hidden" name="action" value="accept" />
<input type="submit" value="I do">
</form>
<form method="post" action="/quick_claim">
<?= $claim_fields ?>
<?= some_csrf_protection ?>
<input type="hidden" name="action" value="back" />
<input type="submit" value="cancel this request">
</form>

The above will successfully check that your user is authenticated and send back the token to the directory service if the user agrees.

Full example workflow

This section will summarize everything into one big example.

Say DIRECTORY is a podcast directory, HOST is a podcast hosting service, PODCAST is a show and CREATOR is a member of this show, allowed to claim the PODCAST on directories.

HOST adds this to the RSS feed:

<podcast:guid>ead4c236-bf58-58c6-a2c6-a6b28d128cb6</podcast:guid>
<podcast:guard auth="https://host.com/studio/quick_claim/" pub="MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==" />

DIRECTORY presents a button "Quick claim".

When CREATOR clicks on it, they are redirected to HOST with this URL :
https://host.com/studio/quick_claim/?guid=ead4c236-bf58-58c6-a2c6-a6b28d128cb6&consumer=https://directory.com/quick_claiming/ead4c236-bf58-58c6-a2c6-a6b28d128cb6&return_path=/return

HOST can use consumer parameter to fetch some data (name, icon, description) through open graph. In this example DIRECTORY use a consumer URL including podcast guid and could use it to add some podcast info into the open graph description.

HOST asks for CREATOR confirmation in a logged only area after verifying on their own responsability if user has indeed access to this podcast.

When CREATOR accept the claim request, the HOST will generate a token, signed by their private key, authenticating the CREATOR decision.

Unsigned :

{
  "guid": "ead4c236-bf58-58c6-a2c6-a6b28d128cb6",
  "accepted": true
}

Signed : eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJndWlkIjoiZWFkNGMyMzYtYmY1OC01OGM2LWEyYzYtYTZiMjhkMTI4Y2I2IiwiYWNjZXB0ZWQiOnRydWV9.eOXYFi9uUSUAKWcI8GdJ15RIhjoCvR0l9TUCPsqhsTYqaGFTwbH6zXzYqIqhxmtSotvL8ZLumP64LRFBjHX5Mw

Note : You can follow along and "translate" online

Decode/Encode online with : https://jwt.io/

Private Key

-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----

Public Key

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
-----END PUBLIC KEY-----

CREATOR are redirected to the DIRECTORY with this URL:

https://directory.com/quick_claiming/ead4c236-bf58-58c6-a2c6-a6b28d128cb6/return?token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJndWlkIjoiZWFkNGMyMzYtYmY1OC01OGM2LWEyYzYtYTZiMjhkMTI4Y2I2IiwiYWNjZXB0ZWQiOnRydWV9.eOXYFi9uUSUAKWcI8GdJ15RIhjoCvR0l9TUCPsqhsTYqaGFTwbH6zXzYqIqhxmtSotvL8ZLumP64LRFBjHX5Mw

DIRECTORY can now check the token parameter to ensure it has been correctly signed with the private key corresponding to the public key seen in the RSS feed, and in this case, the claiming request has been accepted.

Other responses could be, for example :

https://directory.com/quick_claiming/ead4c236-bf58-58c6-a2c6-a6b28d128cb6/return?token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJndWlkIjoiZWFkNGMyMzYtYmY1OC01OGM2LWEyYzYtYTZiMjhkMTI4Y2I2IiwiYWNjZXB0ZWQiOmZhbHNlLCJlcnJvciI6IlVzZXIgY2FuJ3QgYWNjZXNzIHRvIHRoaXMgc2hvdyJ9.MDkZanxlukjQRAj5zd2GoWetAwMWPZs1RU24HdSw8LJm3Z73kL2U4gHMOJUg62LtZdIoH3tktSR0w-1Ltuo4Ig

{
  "guid": "ead4c236-bf58-58c6-a2c6-a6b28d128cb6",
  "accepted": false,
  "error": "User can't access to this show"
}

https://directory.com/quick_claiming/ead4c236-bf58-58c6-a2c6-a6b28d128cb6/return?token=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJndWlkIjoiZWFkNGMyMzYtYmY1OC01OGM2LWEyYzYtYTZiMjhkMTI4Y2I2IiwiYWNjZXB0ZWQiOmZhbHNlLCJlcnJvciI6ImJhY2sifQ.TP8h8Hwh7oRpcuTPXOeqrO46sNwlwC4RLdyMtdFqZQfsS0pUT71_ljoUWq3a0o_hUjuVvPoWnDXar7o2BbLw6w

{
  "guid": "ead4c236-bf58-58c6-a2c6-a6b28d128cb6",
  "accepted": false,
  "error": "back"
}

Here are my thoughs on this idea and how to implement it, feel free to make any remarks about it.

JWT seems to me to be the middle ground between complexity and simplicity for a decentalized authorization system. It only requires the hosting provider to add a private key somewhere and print the pub key on the feed. Signing/Verifying JWT is quick and easy and there are libraries in almost any languages. It basically falls down to something like [verify|sign]JWT(content, priv/pub_key) in most languages.

Open Graph is a nice bonus "out of the box" for permission asker identity.

We could also make this a kind of API spec, and add more data to the token.
Maybe mimick oauth and add a scope param asking specifically for some data, permissions about the feed. (scope="stats,edit,delete,admin" etc)

That's a big subject and a spec of its own. That could be a nice addition but it has to be done in a way we can trust the system and all information it conveys.

@jamescridland
Copy link
Contributor Author

This is great work, @PofMagicfingers . The use of JWT seems good and sensible, given it's based on a standard which is already there.

I'd like to go so far as to suggest that, if done correctly, this would allow podcast hosts to publish RSS feeds without email addresses altogether, and thus avoid privacy and spam issues. That, though, needs the acceptance of someone like Apple or Spotify.

@daveajones
Copy link
Contributor

Does anyone volunteer to write this up into a proposal document? If nobody raises their hand I'll do it. This is sufficiently complex enough to really need it's own doc in the proposal-docs folder.

@daveajones daveajones added the proposal An idea for a new tag label Feb 23, 2022
@theDanielJLewis
Copy link

I love the idea of making this easier! And I have another possible way this could be handled.

Look at how something domain-authentication works with an email service provider: add a TXT record to your DNS, service-provider then polls the domain for the change to match what it's expecting.

We could apply this same kind of claim-code approach to podcasts. It could work like this:

  1. Directory gives the tag code to insert in the feed (more on that below).
  2. Podcaster can either insert it themselves or the directory auto-detects the hosting provider and can use an API endpoint to prepopulate that change. For example, https://captivate.fm/claim-feed/?feedUrl={feed-URL}&claimCode={claim-code}.
  3. Feed gets updated with the tag and the directory checks for it automatically every 30 or 60 seconds until found, or else there's a "check now" button for the podcaster.

Now where to put the tag?

I think that, ideally, it should be a separate channel-level tag, like <podcast:claim platform='podcast-directory-slug' token='495t0df9sg434t3rga432t' />.

But what about for feed-generators that aren't updated or don't support the feature? I think, like Apple Podcasts and a couple other tools I've seen, we could advise putting that token in a standard, editable RSS field, or even some kind of link or emoji/unicode character in the description/content.

This method does, however, depend on the timeliness of the feed's refreshing. If the feed comes directly from the source, then any kind of cache is probably flushed nearly immediately. But if there's a third-party service, like FeedBurner or Podcast Mirror, it can take longer for the change to show up. However, the directory could continue looking for it until found, so the podcaster doesn't have to wait around or do anything further.

@PofMagicfingers
Copy link
Contributor

@theDanielJLewis

Well, actually that's what we've done on the catalog side of podCloud at first. Podcasters could add their show to our catalog and verification was done by looking for a token inside the feed. We had many issues and support requests asking for help with this.

Most of the time podcasters didn't really knew where to put a tag in their RSS feed, or they were using a service or a plugin that didn't let them do that. We moved to just looking for the token anywhere in the feed : episode or channel descriptions... And we still had some users confused about where to add the token, when they could remove it or not, and some didn't want to clutter their description with a garbage token.

We've really "eased" catalog registration since we switched to an email based verification like Spotify.

Also, if we have to add a separate tag for each directory, that means multiple tags to maintain for a solo podcaster, a new UI to let users configure it for a hosting platform... The podcaster need to add a new token for every new platform they register to, we have to maintain a slug list.

Given my experience with that, I'd rather go with our first approch with one tag, and a back and forth between the directory and a source of authority set up in the feed.

@mkadin
Copy link
Contributor

mkadin commented Feb 27, 2022

I agree with @PofMagicfingers that the added layer of security to authenticate the callback to the service as actually being from the hosting company is warranted. This security layer adds some complexity to implementation...but the approach seems solid.

I had thought to bring up @theDanielJLewis's idea earlier as well, but I agree with the cacheing feeds problem. Many hosts cache their feeds for at least a few minutes, so if the solution includes delay, it lacks the smoothness that this solution is looking to create.

Want to be straight with folks in saying that I don't think this feature would be prioritized very highly on our roadmap as long as Apple / Spotify require emails in the feeds. Obviously could be a nice improvement for the user flows of 3rd party services, but I don't see this as a huge problem that podcasters are facing / complaining to us about. Users are used to email auth flows. That doesn't mean this doesn't have value; just noting that this probably wouldn't be super high on the list for us in case that's useful.

@daveajones
Copy link
Contributor

Want to be straight with folks in saying that I don't think this feature would be prioritized very highly on our roadmap as long as Apple / Spotify require emails in the feeds.

This is valuable feedback also Mike. I imagine most hosts feel that way. If we do this type of tag it probably would just be a future-proofing issue that lays road instead of prompting change.

@jamescridland
Copy link
Contributor Author

It depends, I think, how bad the spam issue becomes. If Apple walks the walk about privacy (rather than just talking about it), then they should appreciate the benefit of a better service here.

@PofMagicfingers
Copy link
Contributor

Thanks @daveajones for creating the md doc. We discussed on podcastindex.social about specifying the key algorithm used inside the tag : actually we don't need to, I checked and the algorithm type is included inside the JWT generated by the server.

image

@daveajones
Copy link
Contributor

👍👍

@jamescridland
Copy link
Contributor Author

Closing this, given we chose to go with an opaque podcast:txt service.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal An idea for a new tag
Projects
None yet
Development

No branches or pull requests

7 participants