FormidableLabs / react-native-app-auth

React native bridge for AppAuth - an SDK for communicating with OAuth2 providers
https://commerce.nearform.com/open-source/react-native-app-auth
MIT License
2.04k stars 441 forks source link

Failed refresh token #13

Closed zyofeng closed 6 years ago

zyofeng commented 6 years ago

Hi all

I am quite new to RN as well as app auth but I have previously integrated my Xamarin app with IdentityServer4 using IdentityModel.OidcClient so I have some understanding of the flow.

Please see my following code, the issue I am having is I can not use my saved refresh token to retrieve access token using this.auth.refresh, it always fails unless if I run this.auth.authorize first.

To summarize: no refresh token => authorize => store refresh token => auth.refresh to retrieve access token => api access = works retrieve refresh token => auth.refresh to retrieve access token = Failed refresh token

What have I done wrong here?

componentDidMount() { this.refresh().then(res => { LayoutAnimation.easeInEaseOut(); this.setState({ isLoading: false, devices: res || [] }); }); } async refresh() { let refreshToken = await AsyncStorage.getItem('refreshToken'); if (refreshToken === null) { let authState = await this.authorize();
await AsyncStorage.setItem('refreshToken', authState.refreshToken); refreshToken = authState.refreshToken; } try { const refreshedState = await this.auth.refresh(refreshToken, scopes); let response = await fetch(baseUrl + '/api/client', { method: 'get', headers: { 'Accept': 'application/json', 'Authorization': 'Bearer '+ refreshedState.accessToken } }); return await response.json();
} catch (error) { await AsyncStorage.removeItem('refreshToken'); return await this.refresh(); } }

async authorize() { try { return await this.auth.authorize(scopes); } catch(error) { Alert.alert( 'An error has occured', 'Please try again.' ) return await this.authorize(); } }

zyofeng commented 6 years ago

ok I found what the issue was. In hybrid flow refresh token does not change but in authorization code flow IdentityServer 4 reissues a new refresh token upon each refresh. So I would need to update it. Are there any plan to support hybrid flow? It's recommended for IdentityServer 4 I believe.

Below is the updated code

async refresh() { let refreshToken = await AsyncStorage.getItem('refreshToken'); if (refreshToken === null) { let authState = await this.authorize(); refreshToken = authState.refreshToken; } try { const refreshedState = await this.auth.refresh(refreshToken, scopes); await AsyncStorage.setItem('refreshToken', refreshedState.refreshToken); let response = await fetch(baseUrl + '/api/client', { method: 'get', headers: { 'Accept': 'application/json', 'Authorization': 'Bearer '+ refreshedState.accessToken } }); return await response.json();
} catch (error) { await AsyncStorage.removeItem('refreshToken'); return await this.refresh(); } }

kadikraman commented 6 years ago

Hi @zyofeng - correct me if I'm wrong, but I think in the case when the IdentityServer doesn't issue a new refresh token, you will still be able to use the old one indefinitely?

I think in our example we do not update the refresh token if it is sent back as null and continue to use the old one.

zyofeng commented 6 years ago

Refresh tokens are only valid for up to a maximum of 30 days http://docs.identityserver.io/en/release/topics/refresh_tokens.html

The example is fine, IdentityServer 4 will always send back the refresh token instead of null so there is no harm in updating it. Not quite sure why the refresh token does not get updated by default in hybrid mode.