<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/rss-style.xsl" type="text/xsl"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	     xmlns:dc="http://purl.org/dc/elements/1.1/"
	   xmlns:atom="http://www.w3.org/2005/Atom"
	     xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	  xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>
<channel>
	<title>Auth0 &#8211; Terence Eden’s Blog</title>
	<atom:link href="https://shkspr.mobi/blog/tag/auth0/feed/" rel="self" type="application/rss+xml" />
	<link>https://shkspr.mobi/blog</link>
	<description>Regular nonsense about tech and its effects 🙃</description>
	<lastBuildDate>Fri, 20 Feb 2026 10:41:43 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://shkspr.mobi/blog/wp-content/uploads/2023/07/cropped-avatar-32x32.jpeg</url>
	<title>Auth0 &#8211; Terence Eden’s Blog</title>
	<link>https://shkspr.mobi/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title><![CDATA[Adding "Log In With Mastodon" to Auth0]]></title>
		<link>https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/</link>
					<comments>https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 02 Mar 2026 12:34:48 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[mastodon]]></category>
		<category><![CDATA[MastodonAPI]]></category>
		<category><![CDATA[Social Media]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=67308</guid>

					<description><![CDATA[I use Auth0 to provide social logins for the OpenBenches website. I don&#039;t want to deal with creating user accounts, managing passwords, or anything like that, so Auth0 is perfect for my needs.  There are a wide range of social media logins provided by Auth0 - including the usual suspects like Facebook, Twitter, WordPress, Discord, etc. Sadly, there&#039;s no support for Mastodon.  All is not lost…]]></description>
										<content:encoded><![CDATA[<p>I use <a href="https://auth0.com/">Auth0</a> to provide social logins for the <a href="https://openbenches.org">OpenBenches</a> website. I don't want to deal with creating user accounts, managing passwords, or anything like that, so Auth0 is perfect for my needs.</p>

<p>There are a wide range of <a href="https://auth0.com/learn/social-login">social media logins provided by Auth0</a> - including the usual suspects like Facebook, Twitter, WordPress, Discord, etc. Sadly, there's <a href="https://community.auth0.com/t/custom-social-for-mastodon/103356">no support for Mastodon</a><sup id="fnref:blog"><a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#fn:blog" class="footnote-ref" title="Auth0 did blog about Mastodon a few years ago but never bothered implementing it!" role="doc-noteref">0</a></sup>.</p>

<p>All is not lost though. The Auth0 documentation says:</p>

<blockquote><p>However, you can use Auth0’s Connections API to add any OAuth2 Authorization Server as an identity provider.</p></blockquote>

<p>You can manually add a <em>single</em> Mastodon instance, but that doesn't work with the decentralised nature of the Fediverse. Instead, I've come up with a manual solution which works with <em>any</em> Mastodon server!</p>

<h2 id="background"><a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#background">Background</a></h2>

<p>Every Mastodon<sup id="fnref:masto"><a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#fn:masto" class="footnote-ref" title="I do mean Mastodon; not the wider Fediverse. This only works with sites which have implemented Mastodon's APIs." role="doc-noteref">1</a></sup> server is independent. I have an account on <code>mastodon.social</code> you have an account on <code>whatever.chaos</code>. They are separate servers, albeit running similar software. A generic authenticator needs to work with <em>all</em> these servers. There's no point only allowing log ins from a single server.</p>

<p>Fortuitously, Mastodon allows app developers to automatically create new apps. A few simple lines of code and you will have an API key suitable for <em>read-only</em> access to that server. You can <a href="https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/">read how to instantly create Mastodon API keys</a> or you can <a href="https://github.com/openbenches/openbenches.org/blob/343e4c0169a2af8e567f9444c9cbf5d43d03011a/www/src/Controller/UserController.php#L26">steal my PHP code</a>.</p>

<h2 id="user-experience"><a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#user-experience">User Experience</a></h2>

<p>The user clicks the sign-in button on OpenBenches. They're taken to the Auth0 social login screen:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/01/Auth0-Mastodon.webp" alt="Login screen with several social login buttons." width="1677" height="1258" class="aligncenter size-full wp-image-67317">

<p>The user clicks on Mastodon. This is where Auth0's involvement ends!</p>

<p>The user is asked to provide the URl of their instance:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/01/Enter-server.webp" alt="Screenshot. The site asks for a Mastodon server URl." width="941" height="414" class="aligncenter size-full wp-image-67318">

<p>In the background, my server contacts the Mastodon instance and creates a read-only API key.</p>

<p>The user is asked to sign in to Mastodon.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/01/Masto-login.webp" alt="Screenshot of a login page." width="800" height="900" class="aligncenter size-full wp-image-67319">

<p>The user is asked to authorise read-only access.</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/01/Authorisation.webp" alt="Screenshot. Page asks whether the user wants to authorise OpenBenches for read only access." width="720" height="656" class="aligncenter size-full wp-image-67320">

<p>The user is now signed in and OpenBenches can retrieve their name, avatar image, and other useful information. Hurrah!</p>

<h2 id="auth0"><a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#auth0">Auth0</a></h2>

<p>Once you have  <a href="https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/">created a service to generate API keys</a>, it will need to run on a publicly accessible web server. For example <code>https://example.com/mastodon_login</code>.</p>

<p>Here's what you need to do within your Auth0 tennant:</p>

<ul>
<li>Authentication → Social → Create Connection</li>
<li>At the bottom, choose "Create Custom".</li>
<li>Choose "Authentication" only.</li>
<li>Give your connection a name. This will be visible to users.</li>
<li>"Authorization URL" and "Token URL" have the same value - the URl of your service.</li>
<li>"Client ID" is only visible to you.</li>
<li>"Client Secret" any random password; it won't be used for anything.</li>
<li>Leave everything else in the default state.</li>
</ul>

<p>It should look something like this:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2026/01/Example-Auth0.webp" alt="Screenshot of a form with all the settings filled in." width="891" height="1239" class="aligncenter size-full wp-image-67321">

<p>Click the "Create" button and you're (almost) done.</p>

<h2 id="auth0-icon"><a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#auth0-icon">Auth0 Icon</a></h2>

<p>You will need to <a href="https://shkspr.mobi/blog/2024/12/add-a-custom-icon-to-auth0s-custom-social-integrations/">add a custom icon to the social integration</a>. Annoyingly, there's no way to do it through the web interface, so follow that guide to use the command line.</p>

<h2 id="done"><a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#done">Done!</a></h2>

<p>I'll admit, this isn't the most straightforward thing to implement. Auth0 could make this easier - but it would still rely on users knowing the URl of their home instance.</p>

<p>That said, the Mastodon API is a delight to work with and the read-only permissions reduce risk for all parties.</p>

<div id="footnotes" role="doc-endnotes">
<hr aria-label="Footnotes">
<ol start="0">

<li id="fn:blog">
<p>Auth0 <a href="https://auth0.com/blog/mastdon-for-developers/">did blog about Mastodon a few years ago</a> but never bothered implementing it!&nbsp;<a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#fnref:blog" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

<li id="fn:masto">
<p>I <em>do</em> mean Mastodon; not the wider Fediverse. This only works with sites which have implemented Mastodon's APIs.&nbsp;<a href="https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/#fnref:masto" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=67308&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2026/03/adding-log-in-with-mastodon-to-auth0/feed/</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Adding OpenStreetMap login to Auth0]]></title>
		<link>https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/</link>
					<comments>https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 24 Feb 2026 12:34:21 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<category><![CDATA[developers]]></category>
		<category><![CDATA[oauth]]></category>
		<category><![CDATA[OpenStreetMap]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=67593</guid>

					<description><![CDATA[So you want to add OSM as an OAuth provider to Auth0? Here&#039;s a tip - you do not want to create a custom social connection!  Instead, you need to create an &#34;OpenID Connect&#34; provider. Here&#039;s how.  OpenSteetMap  As per the OAuth documentation you will need to:   Register a new app at https://www.openstreetmap.org/oauth2/applications/ Give it a name that users will recognise Give it a redirect of…]]></description>
										<content:encoded><![CDATA[<p>So you want to add OSM as an OAuth provider to Auth0? Here's a tip - you do <em>not</em> want to create a custom social connection!</p>

<p>Instead, you need to create an "OpenID Connect" provider. Here's how.</p>

<h2 id="opensteetmap"><a href="https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#opensteetmap">OpenSteetMap</a></h2>

<p>As per <a href="https://wiki.openstreetmap.org/wiki/OAuth#Using_OpenStreetMap_as_identity_provider">the OAuth documentation</a> you will need to:</p>

<ul>
<li>Register a new app at <a href="https://www.openstreetmap.org/oauth2/applications/">https://www.openstreetmap.org/oauth2/applications/</a></li>
<li>Give it a name that users will recognise</li>
<li>Give it a redirect of <code>https://Your Auth0 Tenant.eu.auth0.com/login/callback</code></li>
<li>Tick the box for "Sign in using OpenStreetMap"</li>
</ul>

<p>Once created, you will need to securely save your Client ID and Client Secret.</p>

<h2 id="auth0"><a href="https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#auth0">Auth0</a></h2>

<p>These options change frequently, so use this guide with care.</p>

<ul>
<li>Once you have logged in to your Auth0 Tennant, go to Authentication → Enterprise → OpenID Connect → Create Connection</li>
<li>Provide the new connection with the Client ID and Client Secret</li>
<li>Set the "scope" to be <code>openid</code></li>
<li>Set the OpenID Connect Discovery URL to be <code>https://www.openstreetmap.org/.well-known/openid-configuration</code></li>
<li>In the "Login Experience" tick the box for "Display connection as a button"</li>
<li>Set the favicon to be <code>https://blog.openstreetmap.org/wp-content/uploads/2022/07/osm-favicon.png</code> or other suitable graphic</li>
</ul>

<h2 id="next-steps"><a href="https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#next-steps">Next Steps</a></h2>

<p>We're not quite done, sadly.</p>

<p>The details which OSM sends back to Auth0 are limited, so Auth0 is missing a few bits:</p>

<pre><code class="language-json">{
    "created_at": "2026-02-29T12:34:56.772Z",
    "identities": [
        {
            "user_id": "openstreetmap-openid|123456",
            "provider": "oidc",
            "connection": "openstreetmap-openid",
            "isSocial": false
        }
    ],
    "name": "",
    "nickname": "",
    "picture": "https://cdn.auth0.com/avatars/default.png",
    "preferred_username": "Terence Eden",
    "updated_at": "2026-02-04T12:01:33.772Z",
    "user_id": "oidc|openstreetmap-openid|123456",
    "last_ip": "12.34.56.78",
    "last_login": "2026-02-29T12:34:56.772Z",
    "logins_count": 1,
    "blocked_for": [],
    "guardian_authenticators": [],
    "passkeys": []
}
</code></pre>

<p>Annoyingly, Auth0 doesn't set a name or nickname - so you'll need to manually get the <code>preferred_username</code>, or create a "User Map":</p>

<pre><code class="language-json">{
  "mapping_mode": "use_map",
  "attributes": {
    "nickname": "${context.tokenset.preferred_username}",
    "name":     "${context.tokenset.preferred_username}"
  }
}
</code></pre>

<p>There's also no avatar image - only the default one.</p>

<h3 id="getting-the-avatar-image"><a href="https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/#getting-the-avatar-image">Getting the Avatar Image</a></h3>

<p>The <a href="https://wiki.openstreetmap.org/wiki/API_v0.6">OSM API</a> has a method for <a href="https://wiki.openstreetmap.org/wiki/API_v0.6#Methods_for_user_data">getting user data</a>.</p>

<p>For example, here's all my public data: <a href="https://api.openstreetmap.org/api/0.6/user/98672.json">https://api.openstreetmap.org/api/0.6/user/98672.json</a> - thankfully no authorisation required!</p>

<pre><code class="language-json">{
  "user": {
    "id": 98672,
    "display_name": "Terence Eden",
    "img": {
      "href": "https://www.gravatar.com/avatar/52cb49a66755f31abf4df9a6549f0f9c.jpg?s=100&amp;d=https%3A%2F%2Fapi.openstreetmap.org%2Fassets%2Favatar_large-54d681ddaf47c4181b05dbfae378dc0201b393bbad3ff0e68143c3d5f3880ace.png"
    }
  }
}
</code></pre>

<p>Alternatively, you can <a href="https://github.com/microlinkhq/unavatar/issues/488">use the Unavatar service</a> to get the image indirectly.</p>

<p>I hope that's helpful to someone!</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=67593&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2026/02/adding-openstreetmap-login-to-auth0/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Add a custom icon to Auth0's Custom Social integrations]]></title>
		<link>https://shkspr.mobi/blog/2024/12/add-a-custom-icon-to-auth0s-custom-social-integrations/</link>
					<comments>https://shkspr.mobi/blog/2024/12/add-a-custom-icon-to-auth0s-custom-social-integrations/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 09 Dec 2024 12:34:56 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[oauth]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=54309</guid>

					<description><![CDATA[This is so fucking stupid.  There is no way to update the logo of a custom social connection on Auth0 without using the command line.  On literally every other service I&#039;ve used, there&#039;s a little box to upload a logo. But Okta have a funny idea of what developers want.  And, to make matters worse, their documentation contains an error! They don&#039;t listen to community requests or take bug reports,…]]></description>
										<content:encoded><![CDATA[<p>This is <em>so</em> fucking stupid.</p>

<p>There is no way to update the logo of a custom social connection on Auth0 without using the command line.  On literally every other service I've used, there's a little box to upload a logo. But Okta have a funny idea of what developers want.</p>

<p>And, to make matters worse, <a href="https://auth0.com/docs/authenticate/identity-providers/social-identity-providers/oauth2">their documentation contains an error</a>! They don't listen to community requests or take bug reports, so I'm blogging in the hope that this is useful to you.</p>

<h2 id="the-command"><a href="https://shkspr.mobi/blog/2024/12/add-a-custom-icon-to-auth0s-custom-social-integrations/#the-command">The Command</a></h2>

<pre><code class="language-bash">curl --request PATCH \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H 'Authorization: Bearer eyJhb...ZEQ' \
  --url 'https://whatever.eu.auth0.com/api/v2/connections/con_qwerty123456' \
  --data ' ... '
</code></pre>

<p>You will also need to supply some JSON in the <code>data</code> parameter. I've formatted it to be easier to read than the garbage documentation. <em>All</em> of these fields are mandatory.</p>

<pre><code class="language-json">{
  "options": {
    "client_id": "your-app-id",
    "client_secret": "Shhhhhh!",
    "icon_url": "https://example.com/image.svg",
    "scripts": {
      "fetchUserProfile": "???"
    },
    "authorizationURL": "https://example.com/oauth2/authorize",
    "tokenURL": "https://example.com/oauth2/token",
    "scope": "auth"
  },
  "display_name": "Whatever"
}
</code></pre>

<p>OK, but how do you get all those values?</p>

<ul>
<li>Bearer token:

<ul>
<li><a href="https://auth0.com/docs/secure/tokens/access-tokens/management-api-access-tokens">Create a management token</a></li>
<li>The only scope it needs is <code>update:connections</code></li>
</ul></li>
<li>URl

<ul>
<li>This is your normal Auth0 domain name.</li>
<li>The Connection ID at the end can be found in the dashboard of your social connection<br><img src="https://shkspr.mobi/blog/wp-content/uploads/2024/11/social-fs8.png" alt="Screenshot showing an ID field." width="800" height="211" class="aligncenter size-full wp-image-54310"></li>
</ul></li>
<li>Client ID &amp; Secret

<ul>
<li>You set these in the social connection's dashboard.</li>
</ul></li>
<li><code>icon_url</code>

<ul>
<li>Public link to an image. It can be an SVG.</li>
</ul></li>
<li><code>fetchUserProfile</code>

<ul>
<li>Whatever code you want to run. If you don't want any, you can't leave it blank. So type in a couple of characters.</li>
</ul></li>
<li><code>authorizationURL</code> and <code>tokenURL</code>

<ul>
<li>Wherever you want to redirect users to</li>
</ul></li>
<li><code>display_name</code>

<ul>
<li>What you want to show to the user</li>
</ul></li>
</ul>

<p>This is <em>such</em> a load of bollocks! Is it really that hard for the Okta team to put an input field with "type the URl of your logo"?</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=54309&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/12/add-a-custom-icon-to-auth0s-custom-social-integrations/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Creating a generic "Log-in with Mastodon" service]]></title>
		<link>https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/</link>
					<comments>https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Sun, 08 Dec 2024 12:34:47 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<category><![CDATA[MastodonAPI]]></category>
		<category><![CDATA[oauth]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=54287</guid>

					<description><![CDATA[Let&#039;s say you have a website - your_website.tld - and you want people to log in to it using their Mastodon account.  For a traditional social-media site like Twitter or Facebook, you would create an OAuth app on the service that you want. But there are hundreds of Mastodon servers. So you need to create a new app for each one.  That sounds hard, but it isn&#039;t.  Well… not too hard.  Here&#039;s some c…]]></description>
										<content:encoded><![CDATA[<p>Let's say you have a website - <code>your_website.tld</code> - and you want people to log in to it using their Mastodon account.</p>

<p>For a traditional social-media site like Twitter or Facebook, you would create an OAuth app on the service that you want. But there are <em>hundreds</em> of Mastodon servers. So you need to create a new app for each one.  That sounds hard, but it isn't.  Well… not <em>too</em> hard.</p>

<p>Here's some <a href="https://infosec.press/jerry/how-to-user-mastodons-built-on-oauth-provider-as-the-authentication-provider">code adapted from Infosec.press</a>.  It's all written using cURL on the command line - so you should be able to adapt it to your preferred programming language.</p>

<h2 id="register-an-app-on-the-users-mastodon-instance"><a href="https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/#register-an-app-on-the-users-mastodon-instance">Register an app on the user's Mastodon instance</a></h2>

<p>Let's assume the user has given you the name of their Mastodon server - <code>example.social</code></p>

<p>You then send a request for an app to be created on <code>example.social</code> with your website's details. All it requests is the ability to read a user's details, nothing else.</p>

<pre><code class="language-bash">curl -X POST \
 -F "client_name=Login to your_website.tld" \
 -F "redirect_uris=https://your_website.tld/oauth/mastodon?server=example.social&amp;" \
 -F "scopes=read:accounts" \
 -F "website=https://your_website.tld" \
 -A "user-agent/0.1"
 https://example.social/api/v1/apps
</code></pre>

<p>You can set the User Agent to be anything suitable. Some servers won't work if it is omitted.</p>

<p>If the request was successful, <code>example.social</code> will send you this JSON in response:</p>

<pre><code class="language-json">{
  "id": "12345",
  "name": "Login to your_website.tld",
  "website": "https://your_website.tld",
  "scopes": [
    "read:accounts"
  ],
  "redirect_uris": [
    "https://your_website.tld/oauth/mastodon?server=example.social&amp;"
  ],
  "vapid_key": "qwertyuiop-asdfghjkl-zxcvbnm",
  "redirect_uri": "https://your_website.tld/oauth/mastodon?server=example.social&amp;",
  "client_id": "qw_asdfghjkl_zxcvbnm",
  "client_secret": "qwertyuiop1234567890"
}
</code></pre>

<p>Save the server's address, the <code>client_id</code>, and the <code>client_secret</code>. You will need all three later.</p>

<h2 id="the-user-logs-in-to-their-mastodon-instance"><a href="https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/#the-user-logs-in-to-their-mastodon-instance">The user logs in to their Mastodon instance</a></h2>

<p>You need to redirect the user to their server so they can log in. You need to construct a Mastodon URl using the data you received back. Don't forget to URl encode the <code>redirect_uri</code>.</p>

<p>For example, redirect the user to:</p>

<pre><code class="language-_">https://example.social/oauth/authorize
?client_id=qw_asdfghjkl_zxcvbnm
&amp;scope=read:accounts
&amp;redirect_uri=https://your_website.tld/oauth/mastodon%3Fserver=example.social%26
&amp;response_type=code
</code></pre>

<p>When the user visits that URl they can then log in. If they're successful, they'll be redirected back to your server using your specified redirect URI:</p>

<pre><code class="language-_">https://your_website.tld/oauth/mastodon?server=example.social&amp;code=qazwsxedcrfvtgbyhnujm
</code></pre>

<h2 id="get-a-bearer-token"><a href="https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/#get-a-bearer-token">Get a Bearer token</a></h2>

<p>Your website has received a GET request with the user's server name and an authorisation code. As per <a href="https://docs.joinmastodon.org/client/authorized/#token">the Mastodon documentation</a>, your app uses that code to request a Bearer token:</p>

<pre><code class="language-bash">curl -X POST \
 -F "client_id=qw_asdfghjkl_zxcvbnm" \
 -F "client_secret=qwertyuiop1234567890" \
 -F "redirect_uri=https://your_website.tld/oauth/mastodon?server=example.social&amp;" \
 -F "grant_type=authorization_code" \
 -F "code=qazwsxedcrfvtgbyhnujm" \
 -F "scope=read:accounts" \
 -A "user-agent/0.1"
 https://example.social/oauth/token
</code></pre>

<p>If that's worked, the user's server will return a Bearer token like this:</p>

<pre><code class="language-json">{
    "access_token": "abcdefg_123456",
    "token_type": "Bearer",
    "scope": "read:accounts",
    "created_at": 1732916685
}
</code></pre>

<h2 id="get-the-users-details"><a href="https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/#get-the-users-details">Get the user's details</a></h2>

<p>Finally(!) you can use that token to verify the user's credentials with the server:</p>

<pre><code class="language-bash">curl \
 -H "Authorization: Bearer abcdefg_123456" \
 -A "user-agent/0.1"
 https://example.social/api/v1/accounts/verify_credentials
</code></pre>

<p>If that works, you'll get back all the user's details. Something like this:</p>

<pre><code class="language-json">{
    "id": "7112",
    "username": "Edent",
    "acct": "Edent",
    "display_name": "Terence Eden",
    "url": "https://mastodon.social/@Edent",
    "avatar": "https://files.mastodon.social/accounts/avatars/000/007/112/original/37df032a5951b96c.jpg",
...
}
</code></pre>

<h2 id="putting-it-all-together"><a href="https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/#putting-it-all-together">Putting it all together</a></h2>

<ol>
<li>User providers their Mastodon instance's domain name</li>
<li>Your service looks up the domain name in its database

<ul>
<li>If there are no results, request to create a new app on the Mastodon instance and save the returned <code>client_id</code> and <code>client_secret</code></li>
</ul></li>
<li>Redirect the User to their Mastodon instance, using a URl which contains the <code>client_id</code> &amp; callback URl</li>
<li>User logs in to their Mastodon instance</li>
<li>The User's Mastodon instance redirects the User to your service's callback URl which includes an the instance's domain name and User's authorisation code</li>
<li>Your service reads the User's domain name and authorisation code</li>
<li>Your service exchanges those details for a Bearer token</li>
<li>Your service uses the Bearer token to get the User's account details</li>
</ol>

<h2 id="next-steps"><a href="https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/#next-steps">Next steps?</a></h2>

<p>This basic code works. For my next trick, can I integrate it into Auth0?</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=54287&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2024/12/creating-a-generic-log-in-with-mastodon-service/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Auth0 - Adding Twitter Screen Names to a User with Actions]]></title>
		<link>https://shkspr.mobi/blog/2023/10/auth0-adding-twitter-screen-names-to-a-user-with-actions/</link>
					<comments>https://shkspr.mobi/blog/2023/10/auth0-adding-twitter-screen-names-to-a-user-with-actions/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 17 Oct 2023 11:34:18 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=48386</guid>

					<description><![CDATA[Grrrr. Auth0 have a nifty service to let users log in to your site using a social network. Users don&#039;t need an account with you, they can sign in with Twitter, Facebook, GitHub, etc.  But there&#039;s a bug which is five years old. Auth0 doesn&#039;t show the screen name of Twitter users (e.g. @edent).  There was a workaround using their &#34;rules&#34; product. But rules are being removed next month and we all…]]></description>
										<content:encoded><![CDATA[<p>Grrrr. Auth0 have a nifty service to let users log in to your site using a social network. Users don't need an account with you, they can sign in with Twitter, Facebook, GitHub, etc.</p>

<p>But there's <a href="https://community.auth0.com/t/twitter-nickname-is-not-the-same-as-screen-name/17297">a bug which is <strong>five</strong> years old</a>. Auth0 doesn't show the screen name of Twitter users (e.g. <code>@edent</code>).</p>

<p>There <em>was</em> a workaround using their "rules" product. But <a href="https://auth0.com/blog/preparing-for-rules-and-hooks-end-of-life/">rules are being removed next month</a> and we all need to transition to "Actions".  Why? Because fuck you, that's why.  Auth0 have decoded that fixing bugs is less important than <a href="https://auth0.com/blog/branding-updates/">updating their logo yet again</a> despite having <a href="https://auth0.com/blog/introducing-the-auth0-brand-evolution/">updated it a couple of years ago</a>.</p>

<p>Anyway, here's the new action you need:</p>

<pre><code class="language-js">exports.onExecutePostLogin = async (event, api) =&gt; {
  // Set the nickname to be the screen_name for Twitter connections
  if (event.connection.name === 'twitter' &amp;&amp; event.user.screen_name) {
    api.idToken.setCustomClaim('nickname', event.user.screen_name);
  } 
};
</code></pre>

<p>Thanks to <a href="https://community.auth0.com/t/actions-how-do-i-set-an-event-user-nickname/118266/5?u=edent">Gianfranco Parascandolo for helping me figure it out</a>.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=48386&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/10/auth0-adding-twitter-screen-names-to-a-user-with-actions/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[Getting Auth0 user information on non-firewall Symfony pages]]></title>
		<link>https://shkspr.mobi/blog/2023/05/getting-auth0-user-information-on-non-firewall-symfony-pages/</link>
					<comments>https://shkspr.mobi/blog/2023/05/getting-auth0-user-information-on-non-firewall-symfony-pages/#respond</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Mon, 22 May 2023 11:34:10 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<category><![CDATA[HowTo]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[Symfony]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=45789</guid>

					<description><![CDATA[I am using Auth0&#039;s Symfony library to allow users to log in with their social network providers.  It works really well.  Using this firewall configuration, a user who visits /private is successfully taken through the login flow and I can then use $this-&#62;getUser() to see their details.  security:     password_hashers:         Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: …]]></description>
										<content:encoded><![CDATA[<p>I am using <a href="https://github.com/auth0/symfony">Auth0's Symfony library</a> to allow users to log in with their social network providers.  It works really well.</p>

<p>Using this firewall configuration, a user who visits <code>/private</code> is successfully taken through the login flow and I can then use <code>$this-&gt;getUser()</code> to see their details.</p>

<pre><code class="language-yaml">security:
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    providers:
        users_in_memory: { memory: null }
        auth0_provider:
            id: Auth0\Symfony\Security\UserProvider
    firewalls:
        private:
            pattern: ^/private$
            context: user
            stateless: false
            provider: auth0_provider
            custom_authenticators:
                - auth0.authenticator
        main:
            lazy: true
            provider: users_in_memory
</code></pre>

<p>I want some unauthenticated pages to show user information. For example, if the user is logged in then <code>/home</code> should say "Hello $username". If not, it should say "Log in here".</p>

<p>The answer was annoyingly simple - but not documented by Symfony or Auth0.</p>

<p>Change the main firewall to <em>not</em> be lazy:</p>

<pre><code class="language-yaml">        main:
            lazy: false
            provider: users_in_memory
</code></pre>

<p>That then places all the Auth0 information into the <code>$_SESSION</code> global variable.  You can retrieve the user's details with:</p>

<pre><code class="language-php">if ( isset( $_SESSION["_sf2_attributes"]["auth0_session"]["user"] ) ) {
    $user = $_SESSION["_sf2_attributes"]["auth0_session"]["user"];
    $username   = $user["nickname"];
    $avatar     = $user["picture"];
}
</code></pre>

<p>I'm sure there's a more official way to do this, but this quick and dirty hack seems to work pretty well.</p>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=45789&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2023/05/getting-auth0-user-information-on-non-firewall-symfony-pages/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title><![CDATA[You can have user accounts without needing to manage user accounts]]></title>
		<link>https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/</link>
					<comments>https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/#comments</comments>
				<dc:creator><![CDATA[@edent]]></dc:creator>
		<pubDate>Tue, 13 Dec 2022 12:34:29 +0000</pubDate>
				<category><![CDATA[/etc/]]></category>
		<category><![CDATA[Auth0]]></category>
		<category><![CDATA[OpenBenches]]></category>
		<category><![CDATA[Social Media]]></category>
		<guid isPermaLink="false">https://shkspr.mobi/blog/?p=43758</guid>

					<description><![CDATA[The inimitable Simon Willison has a brilliant presentation all about managing side projects:    It is all good advice. But I gently disagree with the slide which says:  Avoid side projects with user accounts If it has user accounts it’s not a side-project, it’s an unpaid job  I get the sentiment. Storing passwords securely is hard. Dealing with users changing their names is hard. Updating avatars …]]></description>
										<content:encoded><![CDATA[<p>The inimitable Simon Willison has a brilliant presentation all about managing side projects:</p>

<iframe title="Massively increase your productivity on personal projects with comprehensive documentation and automated tests" id="talk_frame_938467" class="speakerdeck-iframe" src="//speakerdeck.com/player/1af98590ac6d4c0889c3286dfa37606c" width="620" height="348" style="aspect-ratio:620/348; border:0; padding:0; margin:0; background:transparent;" frameborder="0" allowtransparency="true" allowfullscreen="allowfullscreen"></iframe>

<p>It is all good advice. But I gently disagree with the slide which says:</p>

<blockquote><p>Avoid side projects with user accounts
If it has user accounts it’s not a side-project, it’s an unpaid job</p></blockquote>

<p>I get the sentiment. Storing passwords securely is hard. Dealing with users changing their names is hard. Updating avatars is hard. GDPR is hard. It's just a lot of pain and suffering.</p>

<p>But I <em>still</em> have user accounts on one of my side projects while avoiding all those issues.  Here's how it works on <a href="https://openbenches.org/">OpenBenches</a>.</p>

<h2 id="use-auth0"><a href="https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/#use-auth0">Use Auth0</a></h2>

<p>The <a href="https://auth0.com/">Auth0 service</a> is a multi-vendor OAuth provider. That means I can offer a button which "login" which leads to this screen:</p>

<img src="https://shkspr.mobi/blog/wp-content/uploads/2022/10/Screenshot-2022-10-18-at-19-14-56-Log-in-OpenBenches.png" alt="Login page with buttons for Facebook, Twitter, WordPress, GitHub, and LinkedIn." width="500" height="708" class="aligncenter size-full wp-image-43760">

<p>Auth0 has <a href="https://marketplace.auth0.com/features/social-connections">around 60 different social login providers</a>. I picked the ones which best suited my users' demographic.</p>

<p>So the user hits "Sign In With Twitter<sup id="fnref:twit"><a href="https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/#fn:twit" class="footnote-ref" title="This blog post was written before Alan turned off the 2FA from Twitter. Then turned it back on. Then fired everyone. Then rehired them. Then whined about how much food they ate. Anyway, I've left…" role="doc-noteref">0</a></sup>", gives Twitter their username, password, blood sample, and 2FA token. Twitter gives OpenBenches an authentication token with <em>read only</em> access.</p>

<p>This is important. Even if I were hacked and the tokens stolen, an attacker wouldn't be able to alter the user's account on a different platform.</p>

<p>But, as it is, I don't store the token. So it can't be stolen.</p>

<h2 id="only-store-the-essentials"><a href="https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/#only-store-the-essentials">Only store the essentials</a></h2>

<p>Here's the database which contains my user "accounts":</p>

<pre><code class="language-sql">CREATE TABLE `users` (
  `userID` bigint(20) NOT NULL,
  `provider` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `providerID` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL,
  `name` varchar(128) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
</code></pre>

<p>The <code>userID</code> is just an internally-used key which is incremented. I guess it could be a GUID or something.</p>

<p>The <code>provider</code> is a string like <code>twitter</code> or <code>facebook</code> or <code>linkedin</code> etc.</p>

<p>The <code>providerID</code> is the publicly available ID assigned by the social login service. Twitter gives everyone a number, LinkedIn gives everyone a random string, etc.</p>

<p>The <code>name</code> is the string that the provider calls you. My Twitter name is <code>@edent</code> and my LinkedIn name is <code>Thought Leader Terence Eden</code>.</p>

<p>That's it!  I don't store any access tokens. I don't store a date of birth. I don't store any data unnecessary to running my project.</p>

<h2 id="what-about-updates"><a href="https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/#what-about-updates">What about updates?</a></h2>

<p>I don't store URls to avatar images. Instead, I use <a href="https://cloudinary.com/documentation/social_media_profile_pictures">Cloudinary's Social Avatar service</a>. That's usually as simple as calling <code>res.cloudinary.com/demo/image/twitter/1330457336.jpg</code> - I have to fuss around a little for GitHub and Facebook.  So as soon as the user changes their avatar with their provider, it changes on my site.</p>

<p>Sometimes people change their names. Every time they log in to OpenBenches, I check to see if their <code>name</code> has changed - and update it if it has.</p>

<p>Most services don't let you change your internal ID. So that's fixed.</p>

<h2 id="where-it-goes-wrong"><a href="https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/#where-it-goes-wrong">Where it goes wrong</a></h2>

<p>It isn't all sunshine and roses though.  Here are two things which might give you cause for concern.</p>

<p>What if a user wants to merge their accounts? On OpenBenches we sometimes get users who set up two accounts - and then want data from each of them merged. So far, my answer has just been "no".</p>

<p>What if a user wants to delete their account? Well, they can delete it with Twitter or whoever. If someone asked, I'd probably delete their username from the table.  But it hasn't happened yet.</p>

<h2 id="should-you-do-this"><a href="https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/#should-you-do-this">Should you do this?</a></h2>

<p>I'm not your real dad.  It isn't my job to tell you how to live your life or set up your side projects.</p>

<p>Generally speaking, user accounts are bad news.  We resisted having them on OpenBenches for the longest time - people were anonymous.  But we had lots of users who wanted a leader board so they could show off how many benches they had uploaded.  The only way we could build that is with user accounts.  So we added it. <a href="https://openbenches.org/leaderboard">You can see the Leader Board in action</a>.</p>

<p>Building side projects can be a bit lonely. So it is sometimes nice to develop a community of people who want to use your stuff.</p>

<div id="footnotes" role="doc-endnotes">
<hr aria-label="Footnotes">
<ol start="0">

<li id="fn:twit">
<p>This blog post was written before Alan turned off the 2FA from Twitter. Then turned it back on. Then fired everyone. Then rehired them. Then whined about how much food they ate. Anyway, I've left Twitter. Come <a href="https://mastodon.social/@edent">join me on Mastodon</a>!&nbsp;<a href="https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/#fnref:twit" class="footnote-backref" role="doc-backlink">↩︎</a></p>
</li>

</ol>
</div>
<img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=43758&HTTP_REFERER=RSS" alt="" width="1" height="1" loading="eager">]]></content:encoded>
					
					<wfw:commentRss>https://shkspr.mobi/blog/2022/12/you-can-have-user-accounts-without-needing-to-manage-user-accounts/feed/</wfw:commentRss>
			<slash:comments>7</slash:comments>
		
		
			</item>
	</channel>
</rss>
