OAuth invalid_grant error on coinbase using oauth2_client flutter package - flutter

I am using the oauth2_client package for flutter, connecting to the Coinbase API via OAuth 2.0.
From what I can tell, Coinbase uses the code flow for authentication. This is the same as Github. This is important to note because I can successfully auth into Github using the oauth2_client package for flutter.
To connect to Github I used the existing client:
import 'package:oauth2_client/oauth2_client.dart';
import 'package:meta/meta.dart';
/// Implements an OAuth2 client against GitHub
///
/// In order to use this client you need to first create a new OAuth2 App in the GittHub Developer Settings (https://github.com/settings/developers)
///
class GitHubOAuth2Client extends OAuth2Client {
GitHubOAuth2Client(
{#required String redirectUri, #required String customUriScheme})
: super(
authorizeUrl: 'https://github.com/login/oauth/authorize',
tokenUrl: 'https://github.com/login/oauth/access_token',
redirectUri: redirectUri,
customUriScheme: customUriScheme) {
accessTokenRequestHeaders = {'Accept': 'application/json'};
}
}
Then I created a method to call within the app:
void _oauthMethod() async {
//clientID
String cID = 'x';
//clientSecret
String cSecret = 'y';
OAuth2Client client = GitHubOAuth2Client(
redirectUri: 'my.app://oauth2redirect', customUriScheme: 'my.app');
AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
clientId: cID, clientSecret: cSecret, scopes: ['repo']);
http.Response resp = await http.get('https://api.github.com/user/repos',
headers: {'Authorization': 'Bearer ' + tknResp.accessToken});
}
Calling this function brings up the OAuth page for Github, I can sign in, and if I print resp it shows a list of my repos. As expected.
Using the same method for Coinbase, I first create the new class:
class MyOAuth2Client extends OAuth2Client {
MyOAuth2Client(
{#required String redirectUri, #required String customUriScheme})
: super(
authorizeUrl:
'https://www.coinbase.com/oauth/authorize', //Your service's authorization url
tokenUrl:
'https://api.coinbase.com/oauth/token', //Your service access token url
redirectUri: redirectUri,
customUriScheme: customUriScheme) {
this.accessTokenRequestHeaders = {'Accept': 'application/json'};
}
}
Then I create the method to call:
void _coinbaseAuth() async {
String cID = 'x';
String cSecret = 'y';
MyOAuth2Client client = MyOAuth2Client(
redirectUri: 'my.app://oauth2redirect', customUriScheme: 'my.app');
AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
clientId: cID, clientSecret: cSecret, scopes: ['wallet:user:read']);
print(tknResp);
//code fails
//http.Response resp =
// await http.get('https://api.coinbase.com/v2/user', headers: {
// 'Authorization': 'Bearer ' + tknResp.accessToken,
// 'Content-Type': 'application/json',
// 'Charset': 'utf-8'
// });
}
I can't run the http.Response part, because it is filled with nulls. The tknResp prints:
HTTP 401 - invalid_grant The provided authorization grant is invalid,
expired, revoked, does not match the redirection URI used in the
authorization request, or was issued to another client.
I have tried creating a new OAuth application in Coinbase, however this doesn't work.
Does anyone know why I'm getting this error? It's confusing for me as the code worked with Github using the exact same OAuth flow.

I tested the auth flow manually using postman, which enabled me to get the token.
After some testing, I was able to get the token with the dart package by adding the extra auth code params & disabling PKCE
AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
clientId: cID,
clientSecret: cSecret,
scopes: ["wallet:user:read"],
authCodeParams: {
"grant_type": "authorization_code",
"redirect_uri": "my.app://oauth2redirect"
},
enablePKCE: false,
state: 'OYWjs_95M6jlkvy5');

hi in my case i have problems in the get token with duplicate oauth of coinbase with my app to text.
error "invalid_grant"
To solve, I went to the test account and navigate to the activities section and click x (x) to log out and intend again.
Also applies to other oauth 2
thanks

Related

When making a request to the Vision API Product Search an error occurs "message": "The request is missing a valid API key."

When I register a service account for the Vision API Product Search there's a json file downloaded into my desktop that has the private key. However, when making a request into this api there's no place to send that JSON. I'll show you the documentation and my code.
I didn't understand also what is the curl request and how to send it using the http post request.
And This is my code:
Future<void> uploadProductSet() async {
var projectId = 'estoOne';
var locationId = 'europe-west1';
var url = 'https://vision.googleapis.com/v1/projects/$projectId/locations/$locationId/productSets';
final responseOne = await http
.post(Uri.parse(url),
body: json.encode({
'displayName': 'Product-Set-One',
}))
.catchError((error) {
throw error;
});
print(resoinseOne.body);
}
You have to send your access token with the Authorization header.
The API seems to use the Bearer authentication method.
So set the following header in your http request: Bearer $authToken
You should get the auth-token from the credentials file you've downloaded
So your code should look something like this: (untested)
await http.post(Uri.parse(url),
headers: { 'Authorization': 'Bearer $authToken' },
body: json.encode({
'displayName': 'Product-Set-One',
})).catchError((error) {
throw error
})

Flutter REST api call with Basic auth returns 401, despite correct credentials

I'm trying to call an api from flutter but i keep getting 401 Unauthorized. According to the api documentation it uses basic authentiocation and is UTF-8 encoded. The username and password is provided by the docs and if try the api in a web browser and enter those credentials it goes through and i recieve the data. This is the code i'm using in flutter:
Future<void> requestData() async {
String username = 'abc';
String password = '123';
String basicAuth = 'Basic ' + base64Encode(utf8.encode('$username:$password'));
Response r = await get(
Uri.parse('http://api.example.com'),
headers: {
HttpHeaders.authorizationHeader: basicAuth,
});
print(r.body);
print(r.statusCode);
}
I've also tried this variation which gave the same result:
headers: <String, String>{
'authorization': basicAuth
}
Seeing as the username and password are correct there must be something wrong with how i make the call, but i've tried to do it a bunch of different ways and nothing works. Any help would be greatly appreciated!
As per my experience, there is no need of token or basic auth while doing login. And login is post method not get.
Turns out the documentation i read was outdated/incorrect. The api uses "Digest authentication" which i looked up and was able to implement. This is the code if anyone is interested:
import 'package:http/http.dart';
import 'package:http_auth/http_auth.dart';
...
Response res = await DigestAuthClient("USERNAME", "PASSWORD")
.get(Uri.parse("API_URL")).timeout(const Duration(seconds: 20));

Flutter oAuth2 login with discord. Error with redirect URI

I'm looking to make a button login with discord. For that I use flutter_web_auth but discord shows me an error with the redirect URI.
Invalid OAuth2 redirect_uri
Redirect URI is not supported by client
config discord
I set up flutter_web_auth as requested:
AndroidManifest.xml
<activity android:name="com.linusu.flutter_web_auth.CallbackActivity" >
<intent-filter android:label="flutter_web_auth">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="com.area" />
</intent-filter>
</activity>
function
void loginWithDiscord() async {
// App specific variables
const clientId = 'myClientId' ;
const callbackUrlScheme = 'com.area';
const redirectUri = 'com.area://home'; // OR 'com.area:/';
// Construct the url
final url = Uri.https('discord.com', '/api/oauth2/authorize', {
'response_type': 'code',
'client_id': clientId,
'redirect_uri': redirectUri,
'scope': 'identify',
});
// Present the dialog to the user
final result = await FlutterWebAuth.authenticate(
url: url.toString(), callbackUrlScheme: callbackUrlScheme);
// Extract code from resulting url
final code = Uri.parse(result).queryParameters['code'];
// Use this code to get an access token
final response = await http
.post(Uri.parse('https://discord.com/api/oauth2/authorize'), body: {
'client_id': clientId,
'redirect_uri': redirectUri,
'grant_type': 'authorization_code',
'code': code,
});
// Get the access token from the response
final accessToken = jsonDecode(response.body)['access_token'] as String;
print(accessToken);
}
Your issue is similar with this one (https://github.com/discord/discord-api-docs/issues/5106) :
Discord OAuth2 with mobile require PCKE (Proof Key for Code Exchange) : https://datatracker.ietf.org/doc/html/rfc7636
Proof Key for Code Exchange is an extension to the authorization code flow to prevent CSRF and authorization code injection attacks. The technique involves the client first creating a secret on each authorization request, and then using that secret again when exchanging the authorization code for an access token. This way if the code is intercepted, it will not be useful since the token request relies on the initial secret. (https://www.oauth.com/oauth2-servers/pkce/)
In your case, you need to set up a code_verifier and a code_challenge, the code_challenge will be sent in the authorize request with a code challenge method.
After you get the authorization_code, you will send a request to the token endpoint, you need to use the code_verifier at this moment.
Example :
code_verifier = high-entropy cryptographic random STRING using the
unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
from Section 2.3 of [RFC3986], with a minimum length of 43 characters
and a maximum length of 128 characters.
method to generate a code_verifier
String generateCodeVerifier() {
const String _charset =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
return List.generate(
128, (i) => _charset[Random.secure().nextInt(_charset.length)]).join();
}
After generating the code_verifier, you need to generate the code_challenge from the code_verifier.
String generateCodeChallenge(String codeVerifier) {
var bytes = ascii.encode(codeVerifier);
var digest = sha256.convert(bytes);
String codeChallenge = base64Url
.encode(digest.bytes)
.replaceAll("=", "")
.replaceAll("+", "-")
.replaceAll("/", "_");
return codeChallenge;
}
The S256 method computes the SHA-256 hash of the input and then encodes the hash value using Base64-URL. Then for this example, the code_challenge_method is S256.
Now, you're ready to use the Discord OAuth2 :
final _clientId = "CLIENT_ID";
final _clientSecret = "CLIENT_SECRET";
final _redirectUrl = "com.area://home"; // The one you set on the Discord Portal Developer
final _customUriScheme = "com.area";
late String _codeVerifier;
late String _codeChallenge;
#override
void initState() {
super.initState();
_codeVerifier = generateCodeVerifier();
_codeChallenge = generateCodeChallenge(_codeVerifier);
}
void _loginWithDiscord() async {
// First, get the authorization code, you need to use the code_challenge and code_challenge_method at this moment
final url = Uri.https('discordapp.com', '/api/oauth2/authorize', {
'response_type': 'code',
"redirect_uri": _redirectUrl,
'client_id': _clientId,
'grant_type': 'authorization_code',
'scope': 'identify',
'code_challenge': _codeChallenge,
'code_challenge_method': 'S256',
final result = await FlutterWebAuth.authenticate(
url: url.toString(), callbackUrlScheme: _customUriScheme);
// get the code
var code = result.split("code=")[2].split("&")[0];
// Now that you have the authorization_code, get the TOKEN with a post request, you need to use the code_verifier at this moment
final url2 = Uri.https('discordapp.com', '/api/oauth2/token');
final response = await http.post(
url2,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": _redirectUrl,
"client_id": _clientId,
"client_secret": _clientSecret,
"code_verifier": _codeVerifier
},
);
// if the request is a sucess, get the access token :
if(response.statusCode == 200) {
var jsonResponse = convert.jsonDecode(response.body) as Map<String, dynamic>;
final accessToken = jsonResponse["access_token"];
// your code
} else {
// if the request failed
print(response.statusCode);
print(response.body);
}
});```

How to get refresh token after authenticate via pkce flutter app with keycloak using openid_client?

I have the following KeyCloak Client config, to use pkce authentication flow:
Realm: REALM
Client ID: pkce-client
Client Protocol: openid-connect
Access Type: public
Standard Flow Enabled: ON
Valid Redirect URIs: http://localhost:4200/
Advanced Settings:
Proof Key for Code Exchange Code Challenge Method: S256
When authenticating with flutter App with iOS Simulator via openid_client
https://pub.dev/packages/openid_client like this
authenticate() async {
var uri = Uri.parse('http://$localhost:8180/auth/realms/REALM');
var clientId = 'pkce-client';
var scopes = List<String>.of(['profile', 'openid']);
var port = 4200;
var issuer = await Issuer.discover(uri);
var client = new Client(issuer, clientId);
urlLauncher(String url) async {
if (await canLaunch(url)) {
await launch(url, forceWebView: true);
} else {
throw 'Could not launch $url';
}
}
var authenticator = new Authenticator(
client,
scopes: scopes,
port: port,
urlLancher: urlLauncher,
);
var auth = await authenticator.authorize();
var token= await auth.getTokenResponse();
return token;
}
I get the following response:
How do I get a new access token with the refresh token?
I tried:
POST http://localhost:8180/auth/realms/REALM/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
client_id: pkce-client
grant_type: refresh_token
refresh_token: "received refresh token"
but I get:
{"error":"invalid_client","error_description":"Invalid client credentials"}
How do I need to prepare the request to refresh the access token?
Thanks in advance
One cause of the problem could be that you need to include the client_secret as well in the request. This might be needed if the client is a "confidential" client.
Se the discussion here for further details. Refresh access_token via refresh_token in Keycloak

Apple Sign In with Flutter and Auth0

I have a flutter app and use this package to implement Apple Sign In feature: https://pub.dev/packages/sign_in_with_apple
I get the authorization data from the Apple like below:
userIdentifier = "0XXXX7.6bb65XXXXXXXXXXXXXXXX36.1XXX2"
givenName = "test"
familyName = "signing"
email = "testemail#company.com"
authorizationCode = "c372xxxxxxxa526eexxxxxx1111e.0.rwsex.SwXxxXXXdDj_XxxXXXxxX"
identityToken = "eyJraXxxuxjxxxXxxxXx.eyJXxxxxtXxxxxxxhlYXxxXXxXxXXXXX"
state = null
Then I tries to send the authorizationCode as described here in Step 3: https://auth0.com/docs/connections/nativesocial/apple
And I get the 403 Forbidded {"error":"invalid_grant","error_description":"Invalid authorization code"}
I have configured the settings in the Auth0 dashobard in Social and Applications section.
The login process works well in the web environment but I can not do it in the Flutter.
Could anyone help me with what should I do with the authorizationCode to perform successful login and get Access Token and ID Token from Auth0 in the native app?
Rather a late answer, but I've found following the documentation for the /oauth/token end-point titled "Token Exchange for Native Social" works for me.
Specifically sending that end-point a POST, eg. with this in Dart code:
final appleAuthorizationCode = '???????????????????????';
const url = 'https://$AUTH0_DOMAIN/oauth/token';
final uri = Uri.parse(url);
final result = await http.post(
uri,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: {
"client_id": AUTH0_CLIENT_ID,
"grant_type": 'urn:ietf:params:oauth:grant-type:token-exchange',
"subject_token": appleAuthorizationCode,
"subject_token_type": 'http://auth0.com/oauth/token-type/apple-authz-code',
"scope": 'openid profile offline_access',
},
encoding: Encoding.getByName('utf-8'),
);
print('AUTH0 result: $result');
works for me.