viestat / react-native-spotify

A native module that allows you to use the Spotify SDK API with react-native
MIT License
108 stars 23 forks source link

Login Resume #3

Open patrickml opened 8 years ago

patrickml commented 8 years ago

Is there any way to resume a login with this package? I know you just got start, i'm just curious is there is something undocumented.

viestat commented 8 years ago

I don't understand what are you referring with 'resume login', could you please elaborate on this so I can help you?

patrickml commented 8 years ago

Absolutely, is there currently a way with this package to resume a users session without needing them to re-click the login button. On Tue, Aug 23, 2016 at 10:59 PM Andrés C. Viesca Ruiz < notifications@github.com> wrote:

I don't understand what are you referring with 'resume login', could you please elaborate on this so I can help you?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/viestat/react-native-spotify/issues/3#issuecomment-241944300, or mute the thread https://github.com/notifications/unsubscribe-auth/AHOuuTdT1PDwhbWBWZfmf9on6Ms2Zxouks5qi7N4gaJpZM4JpNdm .

viestat commented 8 years ago

You can call the method loggedIn before calling setClientID:setRedirectURL:setRequestedScopes:callback but it will have a funky behaviour right now. I am working on updating this module to work with the beta 21, I will try to make this clearer on the next version.

patrickml commented 8 years ago

oh awesome -- thanks for making this package btw. I can't wait to see the beta 21 version!

digitaldavenyc commented 8 years ago

@viestat Is the funky behavior loggedIn not persisting if the app has been closed?

patrickml commented 8 years ago

correct, the login screen still shows. I really don't have a way to check if the user is logged in. So they have to click the login button everytime, they don't always have to re-render the credentials, but they do have to hit the button again.

digitaldavenyc commented 8 years ago

Is there anyway to cache login credentials in AsyncStorage or in Keychain?

digitaldavenyc commented 8 years ago

I think this is sort of a big issue because new app versions will likely kill the cache in WebKit (other factors could clear that as well) which will also require re-authenticate.

@patrickml What are the blockers preventing authentication to be persistent?

patrickml commented 8 years ago

@digitaldavenyc I'm not completely sure. For all i know it is persisting, but I know there isn't a way to know if the user was logged back in, i.e. Spotify.on('resume', () => {}) or something like that. Therefore you can't update your UI or have your UI wait to know if the user is logging in / being logged in / or has logged in.

digitaldavenyc commented 8 years ago

@patrickml logging back on isn't the issue as I see it. The user shouldn't have to log back on at all. There should be a cache of some sorts that stores data outside of the app, I'm thinking the Keychain is the most appropriate place to do this.

Do you know what type of authentication the library is using?

patrickml commented 8 years ago

@digitaldavenyc i agree, and im am not too sure as to the answer of your question. The app I made with this was for a 1 time event, so it diving in deeper wasn't needed. @viestat do you have any insight?

viestat commented 8 years ago

@patrickml and @digitaldavenyc , the auth process is OAuth. The way it is implemented right now requires the SPTAudioStreamingController to log in every time the app restarts. As I mentioned before I am working on an update to work with the SDK beta 21. A lot has changed and unfortunately I have had little time to finish it (hopefully soon! ). I am thinking on a better approach for the login process so your input is super valuable. I agree with you, the user should have to login just once. Sorry for the inconvenience...

digitaldavenyc commented 8 years ago

@viestat Thank you, really appreciate you putting time into this library, it's been incredibly helpful. Can't wait for the update. If I can be of any assistance I'd be happy to help.

Regarding the auth, Spotify has a few types of authentication and I was curious which type was in place:

Authorization Code Flow is designed for users to only need to authenticate once, which is why I was asking.

moooji commented 8 years ago

True, Authorization Clode Flow is probably the right choice here. It requires an oauth 2.0 back-end service though (for token refresh etc).

For example: https://github.com/ciaranj/node-oauth

From the Spotify docs: "This method is suitable for long-running applications which the user logs into once. It provides an access token that can be refreshed. Since the token exchange involves sending your secret key, this should happen on a secure location, like a backend service, not from a client like a browser or mobile apps."

digitaldavenyc commented 8 years ago

@moooji That's a good point about requiring a backend API for Authorization Code Flow since your secret key is required. It's possible some developers may not want to build an API to use the library. However, I think it should be an option to go one way other the other because it's a feature of Spotify's auth and apps (like ours) need an auth once solution.

Perhaps there should be two methods for auth? Simple and Code Flow?

moooji commented 8 years ago

Yes, I think so too. It would make this package definitely less attractive if "Authorization Code Flow" would be the only option. Not everyone wants to go through the complexity of setting up an oAuth backend service first just to be able try it out.

So I guess currently this package is using "Implicit Grant Flow". That's fine to get started fast, the only drawbacks are that:

It would be nice to be able to choose between "Authorization Code Flow" and "Implicit Grant Flow". I don't think that the third flow ("Client Credentials Flow") is that interesting, because it cannot access user data.

https://developer.spotify.com/web-api/authorization-guide/#supported-authorization-flows

image

moooji commented 8 years ago

I had a look at the code and it seems "Authorization Code Flow" can be enabled by adding these two lines to startAuth:

[[SPTAuth defaultInstance] setTokenSwapURL:[NSURL URLWithString:tokenSwapURL]];
[[SPTAuth defaultInstance] setTokenRefreshURL:[NSURL URLWithString:tokenRefreshURL]];

I have now a protoype implementation working with a backend service for token swap/refresh and it seems that most pieces are already in place. It should (hopefully) be enough to expose tokenSwapURL and tokenRefreshURL. They would be optional so that it is possible to choose between both flows.

The question is how should the auth signature look like so that we support optional arguments?

Currently, it looks like:

SpotifyAuth.setClientID(clientId, redirectURL, scopes, callback)

We could either have two different methods, one for "Implicit Grant" and one for "Authorization Code":

SpotifyAuth.implicitGrant(clientId, redirectURL, scopes, callback)

SpotifyAuth.authorizationCode(clientId, redirectURL, tokenSwapURL, tokenRefreshURL, scopes, callback)

or one common method that has an option argument:

const options = { clientId, redirectURL, tokenSwapURL, tokenRefreshURL, scopes };
SpotifyAuth.login(options, callback)

What do you think?

moooji commented 8 years ago

Here is a sample implementation of the backend service to get and refresh the access token:

const axios = require('axios');
const express = require('express');
const bodyParser = require('body-parser');

const redirectUri = 'my-spotify-app://callback';
const clientId = '...';
const clientSecret = '...';
const authUrl = 'https://accounts.spotify.com/api/token';

const app = express();
app.use(bodyParser.urlencoded({ extended: true }))

app.post('/swap', function(req, res) {
  const options = {
    code: req.body.code,
    redirect_uri: redirectUri,
    grant_type: 'authorization_code',
  };

  requestAuth(options)
    .then(body => res.send(body))
    .catch(err => res.status(400).send({ error: err.message }));
});

app.post('/refresh', function(req, res) {
  const options = {
    refresh_token: req.body.refresh_token,
    grant_type: 'refresh_token',
  };

  requestAuth(options)
    .then(body => res.send(body))
    .catch(err => res.status(400).send({ error: err.message }));
});

function requestAuth(options) {
  return axios({
    url: authUrl,
    method: 'post',
    params: options,
    auth: {
      username: clientId,
      password: clientSecret,
    },
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    }
  })
  .then(body => body.data);
}

console.log('Listening on 8888');
app.listen(8888);

requires in startAuth:

[[SPTAuth defaultInstance] setRedirectURL:[NSURL URLWithString:redirectURL]];
[[SPTAuth defaultInstance] setTokenSwapURL:[NSURL URLWithString:tokenSwapURL]];
[[SPTAuth defaultInstance] setTokenRefreshURL:[NSURL URLWithString:tokenRefreshURL]];

with for example

redirectURL = 'my-spotify-app://callback'
tokenSwapURL = 'http://my.backend.com:8888/swap'
tokenRefreshURL = 'http://my.backend.com:8888/refresh'
digitaldavenyc commented 8 years ago

@moooji @viestat

My opinion is that this option is a smarter choice:

const options = { clientId, redirectURL, tokenSwapURL, tokenRefreshURL, scopes };
SpotifyAuth.login(options, callback)

I think renaming setClientID to login is a much clearer description of the action that function will perform. I am currently dealing with a show stopping authentication bug with the repo right now that has an open issue so I am unable to test this but it seems like a simple add, you should do a pull request for it.

moooji commented 8 years ago

I have created a PR here: https://github.com/viestat/react-native-spotify/pull/8 which adds the optional Authorization Code Flow.

Implicit Grant Flow

const options = {
    clientID: 'your-clientID',
    redirectURL: 'your-app-schema://callback',
    requestedScopes: ['streaming',...]
};

SpotifyAuth.login(options,(error)=>{console.log(error)});

Authorization Code Flow

const options = {
    clientID: 'your-clientID',
    redirectURL: 'your-app-schema://callback',
    requestedScopes: ['streaming',...],
    tokenSwapURL: 'http://your.backend.com/token/swap',
    tokenRefreshURL: 'http://your.backend.com/token/refresh'
};

SpotifyAuth.login(options,(error)=>{console.log(error)});