Last active
August 18, 2021 07:23
-
-
Save dzolnai/9710849 to your computer and use it in GitHub Desktop.
Retrofit OAuth2 refreshing tokens without modifying all requests.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example.yourapp; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.Iterator; | |
import java.util.List; | |
import com.example.yourapp.AuthenticationModel; | |
import retrofit.client.Header; | |
import retrofit.client.OkClient; | |
import retrofit.client.Request; | |
import retrofit.client.Response; | |
/** | |
* Extends the default Retrofit Http client with a check if the request got denied because the OAuth | |
* token expired. In this case | |
* To be entirely sure that there will be no StackOverFlowErrors using this implementation | |
* Don't let your authentication model use this as a client. | |
**/ | |
public class OAuthClient extends OkClient { | |
private AuthenticationModel _authModel; | |
public OAuthClient(AuthenticationModel authModel){ | |
_authModel = authModel; | |
} | |
/** | |
* Replaces the access token with the new one in the request. | |
* @param request Input request to modify | |
* @return An other request with the same parameters, but the replaced authorization header | |
*/ | |
private Request changeTokenInRequest(Request request){ | |
List<Header> tempHeaders = request.getHeaders(); // this one is an unmodifiable list. | |
List<Header> headers = new ArrayList<Header>(); | |
headers.addAll(tempHeaders); // this one is modifiable | |
Iterator<Header> iter = headers.iterator(); | |
boolean hadAuthHeader = false; | |
// we check if there was an authentication header in the original request | |
while(iter.hasNext()){ | |
Header h = iter.next(); | |
if (h.getName().equals("Authorization")){ | |
iter.remove(); | |
hadAuthHeader = true; | |
} | |
} | |
// if there was an authentication header, replace it with another one containing the new access token. | |
if (hadAuthHeader){ | |
headers.add(new Header("Authorization", "Bearer " + _authModel.getAccessToken())); | |
} | |
// everything stays the same, except the headers | |
return new Request(request.getMethod(), request.getUrl(), headers, request.getBody()); | |
} | |
@Override | |
public Response execute(Request request) throws IOException { | |
Response response = super.execute(request); | |
// 401: Forbidden, 403: Permission denied | |
if (response.getStatus() == 401 || response.getStatus() == 403) { | |
// the next call should be a synchronous call, otherwise it will immediately continue, and use the old token instead. | |
_authModel.refreshToken(); | |
// the headers should be modified because the access token changed | |
Request newRequest = changeTokenInRequest(request); | |
return super.execute(newRequest); | |
} else { | |
return response; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Create your RestAdapter this way | |
RestAdapter adapter = new Builder() | |
.setEndpoint(endpoint) | |
.setClient(new OAuthClient(yourAuthModel) | |
... | |
.build(); | |
// Util to convert TypedInput to Java String | |
public static String stringFromTypedInput(TypedInput input) { | |
try { | |
InputStream istream = input.in(); | |
java.util.Scanner s = new java.util.Scanner(istream).useDelimiter("\\A"); | |
return s.hasNext() ? s.next() : ""; | |
} catch (IOException e) { | |
return null; | |
} | |
} | |
// Example call | |
private interface OAuthInterface { | |
@FormUrlEncoded | |
@POST("/oauth2/access_token") | |
public void getAccessToken(@Field("username") String username, @Field("password") String password, @Field("client_id") String clientId, @Field("client_secret") String clientSecret, @Field("grant_type") String grantType, | |
Callback<TokenData> cb); | |
// NOTICE: This call is synchronous! This should only be called from the OAuthClient. | |
@FormUrlEncoded | |
@POST("/oauth2/access_token") | |
public String refreshToken(@Field("refresh_token") String refreshToken, @Field("client_id") String clientId, @Field("client_secret") String clientSecret, @Field("grant_type") String grantType); | |
} |
What is your AuthenticationModel class?
Can we make synchronous API call in android?
Hi mr.Zolnai can you please update this to retrofit2 ?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This method here: https://gist.github.com/dzolnai/9710849#file-oauthclient-java-L33
can be improved, I suggest not using Iterators to begin with.