» Serverless IndieAuth

Lately, I've been enthralled by the IndieWeb movement, and in the process of rebooting my online identity I decided to make my website adhere to as many IndieWeb standards as possible. This started with simple things, like h-cards and h-entrys, which are easy to implement on a static site. But then I got thinking - the static site host I use, Netlify, has a serverless function service, so I might be able to implement the more complex standards.

I decided to start with IndieAuth, because it seemed like the easiest. IndieAuth is a protocol that fulfills a very similar purpose to OpenID. It allows users to authenticate on a web service using their domain name, with the added benefit of also being able to authorize use of standarized protocols on the user's website. The protocol is decently simple: the origin website redirects to the user's authorization URI, that authorization URI asks permission from the user, and the user is redirected back to the origin with a code. That code can be sent back to the user's website to check its validity. These codes usually expire in about 10 minutes.

This whole exchange has some state involved, such as keeping the valid codes around for 10 minutes. The first approach that came to mind was keeping state stored in a Redis cache, which would probably work but would have required a dependency on an external Redis host. I set the project aside for a few hours, and during that time I remembered that JSON web tokens exist.

With JSON web tokens, I can store the state of the exchange on someone else's state storage and not worry about it. A JSON web token is pretty much a payload and a cryptographic signature. This way, my IndieAuth server can provide a JWT with the origin server's client ID and an expiration time, and I don't have to worry about storing the code. When the origin asks to validate the JWT, I can validate the cryptographic signature. That's the essence of how my serverless IndieAuth service works.

There are two Netlify functions called authorize.js and completeAuthorization.js. I'm a little shy to post the code for authorize.js (because it's an atrocity), but the exchange works as I described above. authorize.js is the route that gives the authorization UI and validates JWTs. The UI's form submits to completeAuthorization.js's route. That generates the JWT and returns a redirect to the origin's redirect URI. In my case, I put a bcrypt hash of the password in an environment variable then used bcrypt to compare with that hash in completeAuthorization, but there are any number of ways you could implement authentication. Here's the code for completeAuthorization:

import*as jwt from'jsonwebtoken';
import*as bcrypt from'bcryptjs';
import{ parse }from'querystring';

exportfunctionhandler(event, context, callback){
const{ me, client_id, redirect_uri, state, password }=parse(event.body);
if(me !=='https://piperswe.me/'){
returncallback(null,{
statusCode:403,
body:'Invalid me.',
});
}
bcrypt.compare(password, process.env.PASSWORD_HASH,(err, res)=>{
if(err){
returncallback(err);
}
if(!res){
returncallback(null,{
statusCode:403,
body:'Incorrect password.',
});
}
const token = jwt.sign({
red: redirect_uri,
}, process.env.JWT_SECRET,{
subject: me,
audience: client_id,
expiresIn:'10 minutes',
});
returncallback(null,{
statusCode:302,
headers:{
Location: redirect_uri +'?code='+encodeURIComponent(token)+'&state='+encodeURIComponent(state),
},
});
})
}

If there's enough interest, I might clean both scripts up a bit so you can plop them in a Netlify site and have IndieAuth.

If you'd like to respond to this, I don't have WebMentions set up yet (though I might be able to do something with Netlify functions and the GitHub API...), so you'll have to comment on the Lobste.rs post.

Piper McCorkle wrote a bit about their serverless implementation of IndieAuth.

Jacky Alciné bookmarked a post by Piper McCorkle.

Published using omnibear.com.