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.
Step 1
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; } }
Step 2
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(); }
Step 3 and 4
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; } }
Advertisement
Answer
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).