Error on redirect
I created a custom Oauth2 client just like you suggested in your article https://dev.to/okrad/oauth2client-implement-oauth2-clients-with-flutter-4jjl . Once the app redirects to authorization page and the user authorizes the client it redirects back to the app with an error. This is the error I am getting
E/flutter ( 3753): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: PlatformException(CANCELED, User canceled login, null)
E/flutter ( 3753): #0 StandardMethodCodec.decodeEnvelope
package:flutter/…/services/message_codecs.dart:569
E/flutter ( 3753): #1 MethodChannel._invokeMethod
package:flutter/…/services/platform_channel.dart:156
E/flutter ( 3753): <asynchronous suspension>
E/flutter ( 3753): #2 MethodChannel.invokeMethod
package:flutter/…/services/platform_channel.dart:329
E/flutter ( 3753): #3 FlutterWebAuth.authenticate
package:flutter_web_auth/flutter_web_auth.dart:35
E/flutter ( 3753): #4 WebAuth.authenticate
package:oauth2_client/src/web_auth.dart:7
E/flutter ( 3753): #5 OAuth2Client.requestAuthorization
package:oauth2_client/oauth2_client.dart:135
E/flutter ( 3753): #6 OAuth2Client.getTokenWithAuthCodeFlow
package:oauth2_client/oauth2_client.dart:73
E/flutter ( 3753): #7 _HomeScreenState.authorize
package:vend_mobile/…/screens/home_screen.dart:49
E/flutter ( 3753): #8 _HomeScreenState.build.<anonymous closure>
package:vend_mobile/…/screens/home_screen.dart:81
E/flutter ( 3753): #9 _InkResponseState._handleTap
package:flutter/…/material/ink_well.dart:779
E/flutter ( 3753): #10 _InkResponseState.build.<anonymous closure>
package:flutter/…/material/ink_well.dart:862
E/flutter ( 3753): #11 GestureRecognizer.invokeCallback
package:flutter/…/gestures/recognizer.dart:182
E/flutter ( 3753): #12 TapGestureRecognizer.handleTapUp
package:flutter/…/gestures/tap.dart:504
E/flutter ( 3753): #13 BaseTapGestureRecognizer._checkUp
package:flutter/…/gestures/tap.dart:282
E/flutter ( 3753): #14 BaseTapGestureRecognizer.handlePrimaryPointer
package:flutter/…/gestures/tap.dart:217
E/flutter ( 3753): #15 PrimaryPointerGestureRecognizer.handleEvent
This is the custom client
class MyCustomOauthClient extends OAuth2Client {
MyCustomOauthClient(
{@required String redirectUri, @required String customUriScheme})
: super(
authorizeUrl: 'AuthorizeURL Ednpoint',
tokenUrl: 'Token URL Endpoint',
redirectUri: redirectUri,
customUriScheme: customUriScheme) {
this.accessTokenRequestHeaders = {'Accept': 'application/json'};
}
}
The success response form the API is '{redirect_uri}?code={code}&domain_prefix={domain_prefix}&state={state}'. I am not sure where this should go. Is it supposed to be in the accessTokenRequestHeaders?. Also the tokenendpoint has an element from the authorization code{domain_prefix} how can I implement that here?
I have been tryting to get this work for a week now and I haven't figuered it yet. Thank you in advance
Hi @myasinu, just to clarify... Is the response you reported an actual response (i.e. you printed it out and are just hiding the param values), or is it just what your API documentation says it should look like?
An error like that could be thrown when the response doesn't contain the expected parameters, but if that's the real response, they seem correct...
One more thing: is the authorization server private to your organization or are you implementing a client for a public service?
Sorry, I closed the issue by mistake...
Hey Okrad thanks for the response. I am not sure if thats the response from the api. This is the api I am using https://docs.vendhq.com/reference/introduction/authorization
Sorry, I closed the issue by mistake...
The issue is still open. I wasn't able to get it work. Can you please have a look at the api link.
Sorry, didn't have much time in the past days...
Looks like those APIs are quite "peculiar" because the access token request url depends on the "domain_prefix" parameter returned by the authorization code response. The access token url must therefore be composed dynamically at runtime, and this complicates things...
I'm trying to figure out what's the best way to handle use cases like these... I currently have a couple of ideas, but I need to let them sink in a bit...
Sorry, didn't have much time in the past days...
Looks like those APIs are quite "peculiar" because the access token request url depends on the "domain_prefix" parameter returned by the authorization code response. The access token url must therefore be composed dynamically at runtime, and this complicates things...
I'm trying to figure out what's the best way to handle use cases like these... I currently have a couple of ideas, but I need to let them sink in a bit...
Thanks for taking time to respond I really appreciate it. A quick fix for the token url can be done by asking the user to enter their domain prefix before redirecting to the authorization page. But i am not sure how the authorization response from the API will effect the outh2 client because of the domain prefix in the response. Once again taking you for your help.
I think the domain prefix won't have any effect... If you can compose the access token url beforehand, you should then pass it to the helper while instantiating...
Another thing: which scopes are you using? oauth2_client currently requires the scopes to be set, but it seems the API don't support the "scope" parameter... I could relax this requirement as even the OAuth2 specs state they are optional, but I'm curious on which scopes you are currently using...
I think the domain prefix won't have any effect... If you can compose the access token url beforehand, you should then pass it to the helper while instantiating...
Another thing: which scopes are you using? oauth2_client currently requires the scopes to be set, but it seems the API don't support the "scope" parameter... I could relax this requirement as even the OAuth2 specs state they are optional, but I'm curious on which scopes you are currently using...
I am leaving the scope parameter as an empty list. I am not sure if thats the right way since the scopes are required by the client class and the api doesn't use them.
I might be wrong here but isn't the issue with setting up the accessTokenRequestHeaders. The Github api actually lets you use the application/json. I am not sure if this api does give you that option. It's authorization response is a string/uri with parameters.
From the API docs it seems no custom headers are needed, so you could get rid of the Accept header. That header is needed for Github because if not specified the authorization server returns the access token response in a "querystring" format, and not in json as expected by the library.
Did you try providing a beforehand crafted token url to the helper? Does it make any difference?
In the next release the scope parameter won't be mandatory, but as long as you pass an empty list, it should make no difference at all. Furthermore, you will be able to compose the token url in between the authorization code and the access token request, using the domain_prefix parameter returned by the API.
From the API docs it seems no custom headers are needed, so you could get rid of the Accept header. That header is needed for Github because if not specified the authorization server returns the access token response in a "querystring" format, and not in json as expected by the library.
Did you try providing a beforehand crafted token url to the helper? Does it make any difference?
In the next release the scope parameter won't be mandatory, but as long as you pass an empty list, it should make no difference at all. Furthermore, you will be able to compose the token url in between the authorization code and the access token request, using the domain_prefix parameter returned by the API.
I removed the access token headers and tried it I am not getting any errors this time around but I still get a null response.
tokenResponse = await _vendOuth2Client.getTokenWithAuthCodeFlow(
clientId: 'Client ID',
scopes: [],
clientSecret: 'Client secret');
and this is the reponse I get in the debug console
D/EGL_emulation(23337): eglCreateContext: 0x6ffecae92660: maj 3 min 1 rcv 4
D/EGL_emulation(23337): eglMakeCurrent: 0x6ffecae92660: ver 3 1 (tinfo 0x6ffee70a7960)
E/eglCodecCommon(23337): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon(23337): glUtilsParamSize: unknow param 0x000082da
D/EGL_emulation(23337): eglMakeCurrent: 0x6ffee91fd940: ver 3 1 (tinfo 0x6ffee7104620)
D/EGL_emulation(23337): eglMakeCurrent: 0x6ffecae92660: ver 3 1 (tinfo 0x6ffee70a7960)
Hi, I just published the new release (1.4.0) with some updates that could simplify your use case, i.e.:
- the scopes are now optional
- you can create the Access Token url at runtime.
Now you can do sometihng like:
var client = OAuth2Client(
authorizeUrl: 'YOUR_AUTHORIZE_URL_ENDPOINT',
tokenUrl: 'FAKE_TOKEN_ENDPOINT',
redirectUri: 'YOUR_REDIRECT_URI',
customUriScheme: 'YOUR_CUSTOM_SCHEME');
var hlp = OAuth2Helper(client,
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET', afterAuthorizationCodeCb: (authResp) {
//Update the access token url using the domain_prefix param
oauthClient.tokenUrl = 'https://' +
authResp.getQueryParam('domain_prefix') +
'.vendhq.com/api/1.0/token';
});
...But to better understand what's happening we need to perform all the steps manually. Can you try with the following:
var client = OAuth2Client(
authorizeUrl: 'YOUR_AUTHORIZE_URL_ENDPOINT',
tokenUrl: 'FAKE_TOKEN_ENDPOINT', //Whatever, we're going to change it later...
redirectUri: 'YOUR_REDIRECT_URI',
customUriScheme: 'YOUR_CUSTOM_SCHEME');
try {
var authResp = await client.requestAuthorization({
clientId: 'YOUR_CLIENT_ID',
//This corresponds to the base64 sha256 of the codeVerifier "12345"
codeChallenge: 'WZRHGrsBESr8wYFZ9sx0tPURuZgG2lmzyvWpwXPKz8U'
);
print(authResp.queryParams);
print(authResp.error);
print(authResp.errorDescription);
if (authResp.isAccessGranted()) {
client.tokenUrl = 'https://' +
authResp.getQueryParam('domain_prefix') +
'.vendhq.com/api/1.0/token';
var tknResp = await client.requestAccessToken(
code: authResp.code,
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
codeVerifier: '12345');
print(tknResp.httpStatusCode);
print(tknResp.error);
print(tknResp.accessToken);
}
else {
print('Access NOT granted!');
}
} catch(e) {
print(e);
}
...and report back what gets printed?
Absolutely. Thanks for the update i will try your suggestions and report back
It seems like I am getting the same error here.
I/flutter (30235): PlatformException(CANCELED, User canceled login, null)
The try statemenet starts but as soon it executes this line
var authResp = await client.requestAuthorization({
clientId: 'YOUR_CLIENT_ID',
//This corresponds to the base64 sha256 of the codeVerifier "12345"
codeChallenge: 'WZRHGrsBESr8wYFZ9sx0tPURuZgG2lmzyvWpwXPKz8U'
);
it throws an error PlatformException.
Ok then... Are you sure the redirect uri is consistent between your client configuration, your manifest file (if you are developing for android) and the Vend app registration panel?
Another thing you could try is opening the authorization url with curl or directly in a web browser and see how the server is responding...
To obtain the authorization url you can do something like:
final authorizeUrl = client.getAuthorizeUrl(
clientId: 'YOUR_CLIENT_ID',
redirectUri: 'YOUR_REDIRECT_URI',
state: '12345',
codeChallenge: 'WZRHGrsBESr8wYFZ9sx0tPURuZgG2lmzyvWpwXPKz8U');
print(authorizeUrl);
Ok then... Are you sure the redirect uri is consistent between your client configuration, your manifest file (if you are developing for android) and the Vend app registration panel?
Another thing you could try is opening the authorization url with curl or directly in a web browser and see how the server is responding...
To obtain the authorization url you can do something like:
final authorizeUrl = client.getAuthorizeUrl( clientId: 'YOUR_CLIENT_ID', redirectUri: 'YOUR_REDIRECT_URI', state: '12345', codeChallenge: 'WZRHGrsBESr8wYFZ9sx0tPURuZgG2lmzyvWpwXPKz8U'); print(authorizeUrl);
Yes I am using the same URI in the manifest as the client configuration. I think the redirect URI is the issue. The api only allows redirect URI that has double slashes. For exmaple myapp://redirect. I changed the rediredt URI to https://myapp/redirect the api gives a response with authorization code in the url but android doesn't redirect back to the app. If I use this myapp://redirect instead the browser will redirect to the App but it will give me the same error.
This is my current configuration: Anrdoid manifest
<data android:scheme="myapp" />
var client = OAuth2Client(
authorizeUrl: 'Authorization Endpoint',
tokenUrl: 'TOKEN Endpoint',
redirectUri: 'myapp://products',
customUriScheme: 'myapp');
My api account is also registered with this rediruct uri = myapp://products
Update: The previous response was when I used chrome browser to go the authentication link. If I use the webview option then after authenticating the screen goes white and this is what is printed in the debug console
D/EGL_emulation( 3925): eglMakeCurrent: 0x6ffeeba45720: ver 3 1 (tinfo 0x6ffeebbde860)
D/EGL_emulation( 3925): eglCreateContext: 0x6ffec04b4000: maj 3 min 1 rcv 4
D/ ( 3925): HostConnection::get() New Host Connection established 0x6ffee711f960, tid 4501
D/EGL_emulation( 3925): eglMakeCurrent: 0x6ffec04b4000: ver 3 1 (tinfo 0x6ffec7c99400)
E/eglCodecCommon( 3925): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon( 3925): glUtilsParamSize: unknow param 0x000082da
D/ ( 3925): HostConnection::get() New Host Connection established 0x6ffeb6588360, tid 4500
D/EGL_emulation( 3925): eglCreateContext: 0x6ffeb649ec60: maj 3 min 1 rcv 4
D/EGL_emulation( 3925): eglMakeCurrent: 0x6ffeb649ec60: ver 3 1 (tinfo 0x6ffeebb52060)
E/eglCodecCommon( 3925): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon( 3925): glUtilsParamSize: unknow param 0x000082da
D/EGL_emulation( 3925): eglMakeCurrent: 0x6ffeeba45720: ver 3 1 (tinfo 0x6ffeebbde860)

I'm getting the same error when trying to log in with Discord's API
My code
class DiscordOAuth2Client extends OAuth2Client {
DiscordOAuth2Client(
{@required String redirectUri, @required String customUriScheme})
: super(
authorizeUrl: 'https://discord.com/api/oauth2/authorize',
//Your service's authorization url
tokenUrl: 'https://discord.com/api/oauth2/token',
//Your service access token url
redirectUri: redirectUri,
customUriScheme: customUriScheme) {
this.accessTokenRequestHeaders = {'Accept': 'application/x-www-form-urlencoded'};
}
}
doAuthWorkflow() async {
var client = DiscordOAuth2Client(
customUriScheme: "com.mrpine.quote-app",
redirectUri: Uri.tryParse("com.mrpine.quote-app://oauth-redirect").toString());
var helper = OAuth2Helper(
client,
grantType: OAuth2Helper.AUTHORIZATION_CODE,
clientId: Secrets.discordID,
scopes: ["identify guilds"],
clientSecret: Secrets.discordSecret,
afterAuthorizationCodeCb: () {
debugPrint("after auth code");
},
);
helper.getToken();
Error message
E/flutter (22548): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: PlatformException(CANCELED, User canceled login, null)
E/flutter (22548): #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:572:7)
E/flutter (22548): #1 MethodChannel._invokeMethod (package:flutter/src/services/platform_channel.dart:161:18)
E/flutter (22548): <asynchronous suspension>
E/flutter (22548): #2 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:334:12)
E/flutter (22548): #3 FlutterWebAuth.authenticate (package:flutter_web_auth/flutter_web_auth.dart:35:27)
E/flutter (22548): #4 WebAuth.authenticate (package:oauth2_client/src/web_auth.dart:7:33)
E/flutter (22548): #5 OAuth2Client.requestAuthorization (package:oauth2_client/oauth2_client.dart:145:40)
E/flutter (22548): #6 OAuth2Client.getTokenWithAuthCodeFlow (package:oauth2_client/oauth2_client.dart:77:26)
E/flutter (22548): #7 OAuth2Helper.fetchToken (package:oauth2_client/oauth2_helper.dart:101:30)
E/flutter (22548): #8 OAuth2Helper.getToken (package:oauth2_client/oauth2_helper.dart:79:23)
E/flutter (22548): <asynchronous suspension>
E/flutter (22548): #9 doAuthWorkflow (package:QuoteApp/oauth2.dart:23:10)
E/flutter (22548): #10 CustomFormState.build.<anonymous closure> (package:QuoteApp/customForm.dart:107:19)
E/flutter (22548): #11 _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:992:19)
E/flutter (22548): #12 _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:1098:38)
E/flutter (22548): #13 GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:184:24)
E/flutter (22548): #14 TapGestureRecognizer.handleTapUp (package:flutter/src/gestures/tap.dart:524:11)
E/flutter (22548): #15 BaseTapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:284:5)
E/flutter (22548): #16 BaseTapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:219:7)
E/flutter (22548): #17 PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:477:9)
E/flutter (22548): #18 PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:78:12)
E/flutter (22548): #19 PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:124:9)
E/flutter (22548): #20 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:377:8)
E/flutter (22548): #21 PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:122:18)
E/flutter (22548): #22 PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:108:7)
E/flutter (22548): #23 GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:220:19)
E/flutter (22548): #24 GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:200:22)
E/flutter (22548): #25 GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:158:7)
E/flutter (22548): #26 GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:104:7)
E/flutter (22548): #27 GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:88:7)
E/flutter (22548): #28 _rootRunUnary (dart:async/zone.dart:1206:13)
E/flutter (22548): #29 _CustomZone.runUnary (dart:async/zone.dart:1100:19)
E/flutter (22548): #30 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1005:7)
E/flutter (22548): #31 _invoke1 (dart:ui/hooks.dart:267:10)
E/flutter (22548): #32 _dispatchPointerDataPacket (dart:ui/hooks.dart:176:5)
Hi @Mr-Pine, each scope should be passed as a separate item in the scopes parameter.
Instead of
scopes: ["identify guilds"],
you should use:
scopes: ["identify", "guilds"],
Let me know if it solves your problem!
Hi, I had tried that originally but unfortunately it didn't work either.
That's strange... Using a similar Discord client I can generate the Token without any problems...
Can you try my test application and see if you get any issues?
Hi. I am using the Oauth2 to studying API connection and I can successfully connect to Google ClassRoom and get student names, courses, etc. Everything is working amazingly! I've followed all steps and it really works fine. However, all my tests were using iOS (my iphone 6 and emulator). When I've tried to use an Android (device or emulator) the callback to my app doesn't happen - I don't know if I am saying that correctly. I've read all material and started another Oauth2 on Google(like you suggested for iOS), but the error persists. What am I doing wrong?
Thanks in advance. Samuel Santos