Tutorial: OAuth2 Access Tokens and Refresh Tokens with Steemconnect

in utopian-io •  7 years ago  (edited)

Motivation

As of late, there have been some phishing scams and the like that have made it such that it's not safe to give out your posting key to anyone. Not even to people you trust. This forced me, for my own applications, to stop requiring posting keys. Instead, we are looking at alternatives like steemconnect that allow users to grant us (developers) a trust.

There are a number of ways to do this, and that's what this tutorial is about. More than anything, it is a technical walkthrough about what many already know and use. I don't want this to be yet another steemconnect tutorial. There are plenty out there for this; however, some have approached me for a more detail-oriented explanation. WITH PICTURES!


What Will I Learn?

  • The oauth2 flow that steemconnect uses
  • How access tokens are actually obtained
  • What refresh tokens are for
  • Where refresh tokens come from
  • How to use a refresh token to obtain an access token

Requirements

This tutorial assumes the reader is building an application with nodejs using the expressjs web framework.

Difficulty

This is an advanced tutorial that will utilize advanced HTTP knowledge and understanding of Oauth2.


The Tutorial

If you are reading this, you should already know what steemconnect is and how you can use it with your application. This tutorial is not about how to use steemconnect.

Retrieving Access Tokens

By now, you are familiar with setting up your sc2-sdk. Your javascript probably has something like this:

  let api = sc2.Initialize({
    app: 'we-resist',
    callbackURL: 'https://we-resist-bot.herokuapp.com/',
    accessToken: req.query.access_token,
    scope: ['vote', 'comment', 'offline']
  })

This does not do very much. It just initializes stuff. In order to actually get an access token. You need to fetch a URL using your browser. How do you do this? And which URL?

The URL you need can be found with api.getLoginURL(), and you can fetch it in one of two ways. If you are executing this from your browser, you would use:

window.location.href = api.getLoginURL();

From your ExpressJS application, you would use the following in your routing configuration:

res.redirect(api.getLoginURL());

image.png

The request actually looks something like:

https://v2.steemconnect.com/oauth2/authorize?client_id=we-resist&redirect_uri=https%3A%2F%2Fwe-resist-bot.herokuapp.com%2F&scope=vote,comment,offline

This is important because what's happening is the client and return url are being sent to steemconnect. This will be important later. What happens is that this initiates the oauth2 flow. Next, you will be expected to login using your posting key or your memo key.

This is just to verify your identity. It isn't actually stored with steemconnect. Steemconnect has no idea about your keys. Once the login is completed and your identity is verified, steemconnect will set you up with a token.

How do you get the access_token? If you observe from the above diagram, steemconnect sends a request back to the application. It knows how to send this request because it is using the redirect_uri we mentioned earlier. It's basically a breadcrumb you left earlier.

The request it follows back to is

https://we-resist-bot.herokuapp.com/?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiY29kZSIsInByb3h5Ijoid2UtcmVzaXN0IiwidXNlciI6InIzNTE1NzRuYzMiLCJzY29wZSI6WyJ2b3RlIiwiY29tbWVudCIsIm9mZmxpbmUiXSwiaWF0IjoxNTIwMDY3MjIyLCJleHAiOjE1MjAwNjc4MjJ9.SXAjVoWuldCzBTU4d2AoavPwkzKgsh1rsRjAxUdKg0Y&username=r351574nc3

It basically calls the application and passes the access_token in the query string.

Refresh tokens

Access tokens are great for applications with a user, but what about refresh tokens? How do you get those?

For this we basically have to start over. All the way over.

  let api = sc2.Initialize({
    app: 'we-resist',
    callbackURL: 'https://we-resist-bot.herokuapp.com/',
    accessToken: req.query.access_token,
    scope: ['vote', 'comment', 'offline']
  })

From here, the URL we are going to use is: https://v2.steemconnect.com/oauth2/authorize?client_id=we-resist&response_type=code&redirect_uri=https%3A%2F%2Fwe-resist-bot.herokuapp.com%2F&scope=vote,comment,offline

I have added my own client_id and redirect_uri. You will want to replace these with your own. Notice the response_type is code. We use this response_type to receive a code from steemconnect instead of an access_token.

Once we have the code, we can use the code to retrieve the refresh_token. This is best done using request.

const rp = require('request-promise');

  return rp({
    method: "POST",
    uri: "https://steemconnect.com/api/oauth2/token",
    body: {
      response_type: "refresh",
      code: req.query.code,
      client_id: "we-resist",
      client_secret: sc2_secret,
      scope: "vote,comment,offline"
    },
    json: true
  })
  .then((results) => {
    let qs = "?access_token=" + results.access_token + "&refresh_token=" + results.refresh_token + "&username=" + results.username
    return res.redirect('/@' + results.username + '/preferences' + qs)
  })

What I have done here is fetched the code from the query string and used it to make a call (not from the browser) directly to steemconnect for an answer. Steemconnect returns a response that looks like this:

{
    access_token: blah blah blah,
    username: blahblahblah,
    refresh_token: blah blah blah
}

Immediately, I have an access_token I can use, and a refresh_token for later. At this stage, I will probably persist the token (database, filesystem, etc...) to use at a later date.

Using the Refresh Token

At some point that I do decide I need to use my refresh_token, how do I do that?

To do this, I created a function called fetch_access_token.

function fetch_access_token(user) {
    return rp({
        method: "POST",
        uri: "https://v2.steemconnect.com/api/oauth2/token",
        body: {
          refresh_token: user.refresh_token,
          client_id: "we-resist",
          client_secret: sc2_secret,
          scope: "vote,comment,offline"
        },
        json: true
      })
}

A couple things to note:

  1. https://v2.steemconnect.com/api/oauth2/token is the url used. Previously, we had been using authorize instead of token. token is specifically for this purpose
  2. refresh_token is sent in the body
  3. client_secret is also required

client_secret is important. Unlike tokens, another cannot be reissued. You definitely do not want to let this into the wrong hands. client_secret is unique to your application. This is the secret given to you when you create your app with steemconnect. It's only real purpose is to process refresh_tokens.

The client_secret can be statically set as part of your application configuration. However, the refresh_token is bound to a user. In the previous steps, we persisted our refresh_token either to a file or a database. That means that in this step, part of the task will be to pull it back out.

This is the flow you are following:

  1. Start processing steemit stream
  2. Fetch users
  3. Get refresh_token from user
  4. Use refresh_token to get access token
  5. Use access_token

Basically, we will follow something like this:

  let api = sc2.Initialize({
    app: 'we-resist',
    scope: ['vote', 'comment', 'offline']
  })

const users = get_users_from_storage();
users.each((user) => {
    fetch_access_token(user)
        .then((token) => {
            api.setAccessToken(token);
            // Do stuff with api
        });
})

That is all you need to do to use the refresh_token.


Curriculum

Other steem tutorials in this series:



Posted on Utopian.io - Rewarding Open Source Contributors

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Moar like this!

Loading...

Thank you for the contribution. It has been approved.

Great tutorial, @r351574nc3!

You can contact us on Discord.
[utopian-moderator]

Hey @r351574nc3 I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x

Great tutorial...It is very good to see that our bot We-Resist has the maximum security possible on steemit.
We don't need peoples keys, we don't store them anywhere.

A pioneer for the following curation bots.

FD.

Damn you deserve much more upvote than this! THIS IS VERY DETAILED AND WELL EXPLAINED! BRAVO ALL IN ALL! you SHOULD consider put this documentation to STEEMCONNECT. others might need this. And it's so hard to find.

Hi! I only get one parameter called code when requesting an offline token.
Do you know how to get the refreshtoken from there?

Loading...