I need to complete Authorization Code Flow with Proof Key for Code Exchange. In step 4, I get an error 400 - bad request {"error":"invalid_request","error_description":"Invalid client secret"}
.
Why need to client secret if it is PKCE. What do I wrong? Do you have any idea?
Body request like
code=abc&grant_type=authorization_code&redirect_uri=spotify-sdk%3A%2F%2Fauth&client_id=abc&code_verifier=abc
Example code verifier: xeJ7Sx1lyUr0A_DAomzewuGn8vNS2cd3ZF2odDlqHEqeYKpxjnYYhpHxOohoo7lf22VNImGiOy_PE07owmDn2VmTWvdKKQ
Example code challenge: N_yPRc_VC8JQJz5dYOuvvM-9cJLdAtEjJ9-lh8Xk_qI
And the same I see into request.
Use PkceUtil
class
class PkceUtil {
private static final int PKCE_BASE64_ENCODE_SETTINGS = Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE;
String generateCodeVerifier(){
SecureRandom random = new SecureRandom();
byte[] codeVerifier = new byte[40];
random.nextBytes(codeVerifier);
return Base64.encodeToString(codeVerifier, PKCE_BASE64_ENCODE_SETTINGS);
}
String generateCodeChallenge(String codeVerifier) {
byte[] bytes = codeVerifier.getBytes(StandardCharsets.UTF_8);
MessageDigest messageDigest = getMessageDigestInstance();
if (messageDigest != null) {
messageDigest.update(bytes);
byte[] digest = messageDigest.digest();
return Base64.encodeToString(digest, PKCE_BASE64_ENCODE_SETTINGS);
}
return "";
}
private MessageDigest getMessageDigestInstance(){
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
Use official android-sdk auth-lib by Spotify
private AuthorizationRequest getAuthRequestCode() {
PkceUtil pkceUtil = new PkceUtil();
codeVerifier = pkceUtil.generateCodeVerifier();
codeChallenge = pkceUtil.generateCodeChallenge(codeVerifier);
return new AuthorizationRequest.Builder(CLIENT_ID, AuthorizationResponse.Type.CODE, getRedirectUri())
.setShowDialog(false)
.setScopes(SCOPE)
.setCustomParam("code_challenge_method", "S256")
.setCustomParam("code_challenge", codeChallenge)
.build();
}
private String getRedirectUri() {
return Uri.parse(REDIRECT_URI).toString();
}
Get code and send request to exchange it
private void onAuthResponse(int resultCode, Intent intent){
AuthorizationResponse response = AuthorizationClient.getResponse(resultCode, intent);
switch (response.getType()) {
case TOKEN:
break;
case CODE:
SpotifyAuthApi api = new SpotifyAuthApi();
SpotifyAuthService spotify = api.getService();
Map<String, Object> map = new HashMap<>();
map.put("client_id", CLIENT_ID);
map.put("grant_type", "authorization_code");
map.put("code", response.getCode());
map.put("redirect_uri", getRedirectUri());
map.put("code_verifier", codeVerifier);
spotify.getAccessToken(map, new Callback<AuthorizationResponse>() {
@Override
public void success(AuthorizationResponse authorizationResponse, Response response) {
}
@Override
public void failure(RetrofitError error) {
// Error 400 - bad request
}
});
break;
case ERROR:
break;
default:
}
}
In order to send request use own AuthApi and AuthService with help Retrofit
public interface SpotifyAuthService {
@POST("/api/token")
@FormUrlEncoded
AuthorizationResponse getAccessToken(@FieldMap Map<String, Object> params);
@POST("/api/token")
@FormUrlEncoded
void getAccessToken(@FieldMap Map<String, Object> params, Callback<AuthorizationResponse> callback);
}
public class SpotifyAuthApi {
private static final String SPOTIFY_ACCOUNTS_ENDPOINT = "https://accounts.spotify.com/";
private final SpotifyAuthService mSpotifyAuthService;
private class WebApiAuthenticator implements RequestInterceptor {
@Override
public void intercept(RequestFacade request) {
request.addHeader("content-type", "application/x-www-form-urlencoded");
}
}
public SpotifyAuthApi() {
Executor httpExecutor = Executors.newSingleThreadExecutor();
MainThreadExecutor callbackExecutor = new MainThreadExecutor();
mSpotifyAuthService = init(httpExecutor, callbackExecutor);
}
private SpotifyAuthService init(Executor httpExecutor, Executor callbackExecutor) {
final RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.BASIC)
.setExecutors(httpExecutor, callbackExecutor)
.setEndpoint(SPOTIFY_ACCOUNTS_ENDPOINT)
.setRequestInterceptor(new SpotifyAuthApi.WebApiAuthenticator())
.build();
return restAdapter.create(SpotifyAuthService.class);
}
public SpotifyAuthService getService() {
return mSpotifyAuthService;
}
}
I'm not familiar with Spotify Android SDK library, but judging by this issue, it does not support PKCE authentication flow and I'm not sure if it creates a valid request when you set custom code_challenge
and code_challenge_method
parameters.
Make sure that this step (2) works, as otherwise the authorization endpoint assumes that you use the normal Authorization Code Flow and expects a client_secret
(in step 4).
According log, request from library is correct:
com.spotify.sdk.android.auth.LoginActivity: Spotify Auth starting with the request [https://accounts.spotify.com/authorize?client_id=abc&response_type=code&redirect_uri=spotify-sdk%3A%2F%2Fauth&show_dialog=false&utm_source=spotify-sdk&utm_medium=android-sdk&utm_campaign=android-sdk&scope=playlist-read-private&code_challenge_method=S256&code_challenge=abc]
At least all the fields are there.@Viewed I would still advise you to try making this request using some other client and try using the obtained values in the next steps to make sure in which step you have the problem.