dmfs / oauth2-essentials

An OAuth2 client implementation based on http-client-essentials.
Apache License 2.0
86 stars 21 forks source link

RefreshTokenGrant with refresh token string #50

Closed yurtesen closed 6 years ago

yurtesen commented 6 years ago

It is kind of silly but in some cases one may have the refresh token as string and not as OAuth2AccessToken.

For example android account manager is incapable of storing token as an object and I think it is not possible to recreate OAuth2AccessToken object with information stored in android account data.

Makes refreshing token kind of difficult.... :( What could be the solution else than using the refresh token string directly?

dmfs commented 6 years ago

right, we don't have a mechanism to serialize tokens so far. It's not that hard though. OAuth2AccessToken gives you access to all fields, so you can store them individually (for a token refresh you only need the refresh token and the scope). Once you want to sent the token refresh request use the data to reconstruct the OAuth2AccessToken. Note that OAuth2AccessToken is just an interface so you can easily create your own using the stored data.

final String refreshToken = loadRefreshToken();
final String scope = loadScopeString();
OAuth2AccessToken token = new OAuth2AccessToken() {
    public CharSequence accessToken() throws ProtocolException {
        throw new UnsupportedOperationException("accessToken not present");
    }

    public CharSequence tokenType() throws ProtocolException {
        throw new UnsupportedOperationException("tokenType not present");
    }

    public boolean hasRefreshToken() {
       return true;
    }

    public CharSequence refreshToken() throws ProtocolException {
       return refreshToken;
    }

    public DateTime expirationDate() throws ProtocolException {
        throw new UnsupportedOperationException("expirationDate not present");
    }

    public OAuth2Scope scope() throws ProtocolException {
        return new StringScope(scope);
    }
}

assuming loadRefreshToken() and loadScopeString() load the refresh token and scope you have stored, this should be enough to refresh the token with a TokenRefreshGrant.

yurtesen commented 6 years ago

Actually, I solved the problem similarly to what you explain but I think little bit more tidy using JSONObject. But I am not sure what the scope variable does when creating JsonAccessToken. I am guessing not important for my case. It is just not clear to me how it is different than the token scope.

Below is what I do, hopefully it may be useful to some android developers (unless there is a better way that I don't know):

I make a JSONObject and store the variables in there and then use toString() to store it as string.

            JSONObject tokenJson = new JSONObject();
            tokenJson.put("access_token", token.accessToken());
            tokenJson.put("refresh_token", token.refreshToken());
            tokenJson.put("expires_in", token.expirationDate());
            tokenJson.put("scope", token.scope());
            tokenJson.put("token_type", token.tokenType());

In this case I store data in jsonToken = tokenJson.toString() as string. Then I re-generate the token from string like this:

            JSONObject jsonObjToken = new JSONObject(jsonToken);
            OAuth2AccessToken token = new JsonAccessToken(jsonObjToken, null);
            new TokenRefreshGrant(client, token).accessToken(executor);

This seem to be working good and quite tidily creates the token back. Still it would have been nice to have a native way to serialize tokens. Luckily I don't have to touch this code ever again once it is working :)

Thanks for the response!

dmfs commented 6 years ago

Right, that works too. In future versions we'll make a few changes to the OAuth2AccessToken interface. I want to get rid of the hasRefreshToken() method and instead provide a simple way to serialize the token and create a TokenRefreshGrant from the serialized version. A client usually doesn't need direct access to the refresh token as long as there is a way to store and restore the OAuth2AccessToken object.

The scope is the scope of your access token. When you start an authentication flow you usually request a certain access scope. This is, however, not necessarily the scope you get access to. The protocol allows the server to return a different scope. The token refresh grant allows a client to request a reduced scope. That's why you can specify a scope when you refresh a token. Our implementation automatically requests the scope of the current access token.

yurtesen commented 6 years ago

What confused me is that I thought scope was just a text defining what is the token for. But the OAuth2Scope() has methods like isEmpty(), hasToken(), tokenCount() which made me think it maybe has a different meaning here. But anyway, I don't have to understand everything :) thank you for your quick response!

dmfs commented 6 years ago

An OAuth "scope" is a list of tokens which describe the "features" you request or get access to. For instance, when you authenticate with Google you can request read or write access to calendars, contacts, tasks, mail, code etc. Check out the list of scopes on Google: https://developers.google.com/identity/protocols/googlescopes So in order to read Google calendar and Google contacts you'd request the scope https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/contacts.readonly. Google will then prompt the user with the features your client requested. If you also want to write these APIs you'd need another scope token. Some services may allow the user to grant access to a subset of the requested features and remove features he doesn't want to grant access to. For these services you need to check which scope tokens the OAuth2AccessToken actually contains. Often you also have to select he scopes you want to support when you register your client and you can only request a subset of these. Some services don't use scope tokens and always give you full access to the features your client is registered for. The OAuth2Scope type is meant to allow you to check if a specific token is in the list.