How to connect a Python Flask web app with Hive Keychain by way of Javascript for Hive authentication
If you want to see where this is going: login to this site with your Hive Keychain
I'm learning to code. Well I'm re-learning to code at any rate. The first time I learned to code it was by reading, re-reading and reading again the ZX81 BASIC manual. I kept on coding from then until around 1997 when I completed my PhD in computational physics... and then I pretty much stopped coding.
But I'm back and I after a few small projects like recording the level of the Kinneret, also known as the Sea of Galilee in Israel, I decided to start a bit of a monster project to completely revolutionising the way Podcasts are funded.
That project is ongoing but along the way I wanted to get Hive Keychain working as a method to authenticate a user with my Python Flask based server application.
You can try this out on a demo here - there's not much working, I kept it to the bare minimum, but you can log in and log out using Hive KeyChain.
This is based on a terrific set of videos explaining the Python Flask micro web server created by an absolutely brilliant teacher, Corey Schafer.
Full playlist for Corey's Flask Blog
The base code for this comes from Corey's 6th video in the series:
Python Flask Tutorial: Full-Featured Web App Part 6 - User Authentication
And his code is on GitHub
There's one extra step about creating virtual environments which this video is good for
The Hive Keychain Parts
The bits I added can all be found in my Github repository: How to use Hive Keychain and Python Flask
The bulk of the changes are in the HTML Jinja2 template called login.html
and in the routes.py
file.
There was a need to combine Python with Javascript and I suck at Javascript. Thankfully I got some great help and that is what I want to share here. My Javascript is horribly wordy, but I hope it's easy to follow what is going on.
Huge thanks to @rishi556, @stoodkev and @ausbitbank all of whom helped me out.
I really hope putting this up here helps others!
<button class="btn btn-primary" id="Check Keychain" name="check-keychain" onClick="hiveKeychainSign()">Hive KeyChain Login</button>
(html comment removed: Hive Keychain javascript part )
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
function hiveKeychainSign(){
let name = document.querySelector("#acc_name").value;
console.log(name);
const keychain = window.hive_keychain;
if (!name) {
// need a name
console.log('need a name');
return
}
const signedMessageObj = { type: 'login', address: name, page: window.location.href };
const messageObj = { signed_message: signedMessageObj, timestamp: parseInt(new Date().getTime() / 1000, 10) };
keychain.requestSignBuffer(name, JSON.stringify(messageObj), 'Posting', response => {
if (!response.success) { return; }
//Successfully logged in
console.log(response);
//We added stuff here
axios.post("/hive/login", response).then((res) => {
console.log(res)
let data = res.data;
//You'd probably want to give the url back in as a json.
//Whatever you send back will be save in data. Here' i'm assuming the format
//data = {podcaster : "https://google.com"}
window.location.href = `${data.loadPage}`;
}).catch((err) => {
console.log(err);
//Deal with any error here
})
});
};
</script>
(html comment removed: Hive Keychain javascript part )
@app.route("/hive/login", methods=['GET','POST'])
def hive_login():
""" Handle the answer from the Hive Keychain browser extension """
if current_user.is_authenticated:
return redirect(url_for('home'))
if request.method == 'POST' and request.data:
ans = json.loads(request.data.decode('utf-8'))
if ans['success'] and validate_hivekeychain_ans(ans):
acc_name = ans['data']['username']
user = User.query.filter_by(username = acc_name).first()
if user:
login_user(user, remember=True)
flash(f'Welcome back - @{user.username}', 'info')
app.logger.info(f'{acc_name} logged in successfully')
return make_response({'loadPage':url_for('home') }, 200)
# return redirect(url_for('podcaster.dashboard'))
else:
user = User(username=acc_name)
db.session.add(user)
db.session.commit()
result = login_user(user, remember=True)
flash(f'Welcome - @{user.username}', 'info')
app.logger.info(f'{acc_name} logged in for the first time')
return make_response({'loadPage':url_for('home') }, 200)
# return redirect(url_for('podcaster.dashboard'))
else:
flash('Not Authorised','danger')
return make_response({'loadPage':url_for('login') }, 401)
def validate_hivekeychain_ans(ans):
""" takes in the answer from hivekeychain and checks everything """
""" https://bit.ly/keychainpython """
acc_name = ans['data']['username']
pubkey = PublicKey(ans['publicKey'])
enc_msg = ans['data']['message']
signature = ans['result']
msgkey = verify_message(enc_msg, unhexlify(signature))
pk = PublicKey(hexlify(msgkey).decode("ascii"))
if str(pk) == str(pubkey):
app.logger.info(f'{acc_name} SUCCESS: signature matches given pubkey')
acc = Account(acc_name, lazy=True)
match = False, 0
for key in acc['posting']['key_auths']:
match = match or ans['publicKey'] in key
if match:
app.logger.info(f'{acc_name} Matches public key from Hive')
mtime = json.loads(enc_msg)['timestamp']
time_since = time.time() - mtime
if time_since < 30:
app.logger.info(f'{acc_name} SUCCESS: in {time_since} seconds')
return True , time_since
else:
app.logger.warning(f'{acc_name} ERROR: answer took too long.')
else:
app.logger.warning(f'{acc_name} ERROR: message was signed with a different key')
return False, 0
- Vote for APSHamilton's Witness KeyChain or HiveSigner
- Vote for APSHamilton's Witness direct with HiveSigner
- Get Brave
- Use my referral link for crypto.com to sign up and we both get $50 USD
- Bittrex is my Favorite fully featured Crypto Exchange
- Sign up for BlockFi
- Find my videos on 3speak
- Join the JPBLiberty Class Action law suit
- Verify my ID and Send me a direct message on Keybase
Believe it or not I was searching for this last week .
I basically use only python and I am not a web developer but I know some basic JS but no matter what I tried I couldn't get it to work . I will go through whatever you have put up :)
Thanks a lot.
This was insanely difficult for me too but I'm so glad I got it working and I'm sure it will help others.
The javascript stuff completely confuses me, there are so many competing ways to do everything including declaring functions that I'm always left confused by anyone else's code.
https://twitter.com/brianoflondon/status/1367196755197124609
Congratulations @brianoflondon! You received a personal badge!
Participate in the next Power Up Day and try to power-up more HIVE to get a bigger Power-Bee.
May the Hive Power be with you!
You can view your badges on your board and compare yourself to others in the Ranking
Check out the last post from @hivebuzz:
Dear @brianoflondon
Programming language is something that is worldwide, whoever masters it, has the possibility to automate many processes, and for sure this will simplify your life.
Yours, Piotr
Going to try to scale the mountain of doing what you did but for Django. Wish me luck! 🤞
Find me on discord or Telegram if you get stuck!