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 https://www.youtube.com/watch?v=ojzNmvkNfqc
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>
<!-- 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>
<!-- 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