Cannot get access token after successful SSO
I am able to obtain authorization_code from OAuthLogin, but cannot get access token from oauth.wildapricot.org with it.
The failure occurs due to CORS although I believe I am making the request from the same origin (SSOfail_CORS.png).
If I disable CORS, the failure behaves differently. A pop-up is shown asking for username+password (SSOfail_noCORS.png). This pop-up keeps repeating after entering valid credentials, and must eventually be canceled to continue.
Here is the code that makes access token request:
$.post(
"https://oauth.wildapricot.org/auth/token",
{
authorization_code: code,
client_id: "xxxxxxx",
client_secret: "xxxxxxxxxxxxxxxxxxxxxxx",
redirect_uri: "https://rbc.wildapricot.org/Schedule",
scope: "auto"
},
function(data, status){
alert("Data " + JSON.stringify(data) + "\nStatus: " + status);
}
);
Thanks for any help on this!
The reason is that you pass client_id and client_secret as a form parameters, but they should be passed in Authorization header.
API is expected to be used from server code or from standalone application. Currently JS applications are not supported. Nature of JS applications does not allow to keep secret, so any user can extract auth token from JS application. I would recommend to implement all interactions with public API using some server-side app. It can be PHP / python or even google script app.
-
Marios Nicolaou commented
Hi Eric
any chance you can share your working code?
I am trying to do this with PHP and curl and I am following the documentation but I am not succeeding
BW
Marios
-
Eric Downer commented
Hi Dmitry,
Thank you for this. Turns out one of the parameters has to be "code" and not "authorization_code" for the token request. Thanks for pointing me in the right direction.
Cheers,
Eric -
Dmitry Smirnov commented
Hi Eric,
I have a working sample of asp.net mvc application, which works with single sign on. You can find a code on github https://github.com/WildApricot/ApiSamples/tree/master/C%23/OAuthClientTestApp.
Dmitry
Mobile and API developer at Wild Apricot -
Eric Downer commented
I'm also having this issue. In my case, the API is simply timing out. I am also unsure if I should be using http://oauth.wildapricot.org/oauth/token (which times out) or https://oauth.wildapricot.org/auth/token (which throws a 500). I feel like what I'm trying to do is fairly simple, but I can't seem to suss it out. Any help would be appreciated. I'm using a .NET forms application:
string code = Request.QueryString["code"].ToString();
string client_id = "xxxxxxxx";
string client_secret = "xxxxxxxx";
string redirect_uri = Server.UrlEncode("http://packfreight/ship/WATest2.aspx");
string scope = "contacts_me";
string grant_type = "authorization_code";
string claimed_account_id = "xxxxxxxx";
string state = "pf001";
string uri = "http://oauth.wildapricot.org/oauth/token";string postData = "grant_type=" + grant_type;
postData += ("&authorization_code=" + code);
postData += ("&client_id=" + client_id);
postData += ("&redirect_uri=" + redirect_uri);
postData += ("&scope=" + scope);
byte[] data = Encoding.UTF8.GetBytes(postData);WebRequest req = (HttpWebRequest)WebRequest.Create(uri);
string encoded = System.Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(client_id + ":" + client_secret));
req.Headers.Add(HttpRequestHeader.Authorization, "Basic " + encoded);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = data.Length;
Stream newStream = req.GetRequestStream();
newStream.Write(data, 0, data.Length);
newStream.Close();WebResponse response = req.GetResponse();
Stream responseStream = response.GetResponseStream();
StreamReader responseReader = new StreamReader(responseStream);
string result = responseReader.ReadToEnd(); -
Curtis Minns commented
1) Users is directed to a URL on a hosted server outside Wild Apricot: The URL looks something like this: http://devsite.mynonprofit.org/call/CALL_ID
2) The URL returns 302 Found status code and sends the response location: https://mynonprofit.wildapricot.org/sys/login/OAuthLogin?client_id=CLIENT_ID&claimed_account_id=ACCOUNT_ID&state=CALL_ID&scope=contacts_me&redirect_uri=http://devsite.mynonprofit.org/app&response_type=authorization_code
3) The user is redirected to authenticate on the Wild Apricot single sign-on screen
4) After successful authentication, the Wild Apricot SSO page returns a 302 Found status code and sends the response location: http://devsite.mynonprofit.org/app?code=AUTHORIZATION_CODE&state=CALL_ID
5) The user is redirected to the webapp on the external site with the authorization code. Python makes a call to request an authentication token using the authorization code. The HTTP request (without sensitive data) is:
POST http://oauth.wildapricot.org/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic Y2xpZW50X2lkOmNsaWVudF9wYXNzd29yZA==b'client_id=CLIENT_ID&grant_type=authorization_code&redirect_uri=http://devsite.mynonprofit.org/app&scope=contacts_me&authorization_code=AUTHORIZATION_CODE';
6) The response I get from the Request URL http://oauth.wildapricot.org/oauth/token is HTTP Error 500
-
I don't understand what's wrong with code, but due to server logs, looks like server does not get data. Could yoy please dump the HTTP request content (without sencitive data) and post in here or in gist?
-
Curtis Minns commented
Thanks Dmitry,
I am being redirected back to my site after successful OAuthLogin. The authorization_code appears to be returned as the query string parameter "code" along with "state" (blank if nothing was passed with the original request). I am using the "code" value as the authorization_code parameter in the authentication token request. I am using all of the parameters specified in your example but am now receiving a 500 Internal Server Error when opening the URL in my python code:
data = {
"grant_type": "authorization_code",
"authorization_code": code,
"client_id": self.client_id,
"redirect_uri": redirect_uri,
"scope": "contacts_me"
}
encoded_data = urllib.parse.urlencode(data).encode()
request = urllib.request.Request(self.auth_endpoint, encoded_data, method="POST")
request.add_header("ContentType", "application/x-www-form-urlencoded")
auth_header = base64.standard_b64encode((self.client_id + ':' + self.client_secret).encode()).decode()
request.add_header("Authorization", 'Basic ' + auth_header)
response = urllib.request.urlopen(request)Receiving: urllib.error.HTTPError: HTTP Error 500: Internal Server Error
Any suggestions?
-
Hi Curt,
clienty_id and client_secret should be passed in authorization header, delimited by colon and base64 encoded. Authorization scheme is Basic.
grant_type should be passed as a form parameter with value "authorization_code".
client_id should also be passed as a form parameter (yes, in this scenario is should be passed both in form params and in authorization header).
Example
POST http://oauth.wildapricot.org/oauth/tokenAuthorization: Basic Y2xpZW50X2lkOmNsaWVudF9wYXNzd29yZA==
grant_type=authorization_code&authorization_code=09827394752790347&client_id=CLIENT_ID&redirect_uri=http://callback.yourdomain.com&scope=contacts_me
-
Curtis Minns commented
How are the client_id and client_secret passed in the Authorization header? Are they delimited by a colon and encoded in base64? Is there a specific grant_type that also needs to be passed as a form parameter? Right now I'm passing authorization_code, redirect_uri, and scope as form parameters.
I'm using a server-side python app. I keep getting HTTP Error 400: {"error":"unsupported_grant_type","error_description":"unsupported_grant_type","error_uri":null}
Thanks.
-
Steve Riegel commented
Dmitry,
Thanks for the 401 explanation. I moved client_id/secret to the Authorization header and now get the following response from the auth service:
https://oauth.wildapricot.org/auth/token
Failed to load resource: the server responded with a status of 400 ({"error":"unsupported_grant_type","error_description":"unsupported_grant_type","error_uri":null})Is this response also because cross domain is not allowed for auth service?
I just want to complete my understanding on this. I realize it is ultimately not possible from JS.
Again, my only wish is to have some form of stable ID of the logged-in WA user available to my restricted-access webpage. I added my vote to the topic you noted. The ID can be anything (ex: hash of memberID), and I do not need any further info. I feel this is a reasonable and secure feature for a restricted-access webpage.
Thanks again.
-
Steve,
I have just understood why you have 401 error when cross domain requests enabled in browser settings. The reason is that you pass client_id and client_secret as a form parameters, but they should be passed in Authorization header.
>> How can any app use api and auth service if cross domain is not permitted?
API is expected to be used from server code or from standalone application. Currently JS applications are not supported. Nature of JS applications does not allow to keep secret, so any user can extract auth token from JS application. I would recommend to implement all interactions with public API using some server-side app. It can be PHP / python or even google script app (https://developers.google.com/apps-script/guides/standalone).
>> In any case, it seems that at least the auth service (auth/token) should permit cross domain requests.
Could you please create new topic on our wishlist (http://forums.wildapricot.com/forums/308932-wishlist)? If topic exists we can track how many users vote for this feature.
>> Is there anything available to the webpage environment for that?
Your request looks similar to this topic: http://forums.wildapricot.com/forums/309658-developers/suggestions/8828014-javascript-access-to-contact-member-event-data
If it is what you need, then you can vote for it. -
Steve Riegel commented
My mistake. The api does not allow cross domain. I was running apikey tests with CORS disabled.
So how can the API be useful at all without cross domain support? Cross domain with credentials or SSO/auth-token should at least be permitted.
And, hopefully somehow, can I finally get the user ID of the logged-in user? Is there really no way to obtain this?
I understand protecting most info from program access (when logged-in), but some consistent unique identifier for the user seems reasonable (does not have to be the actual ID). Is there anything available to the webpage environment for that? If not, please consider a single api call that could provide this (cross-domain). I just need some consistent public handle for referencing the user. Is that a bad thing?
Thanks!
-
Steve Riegel commented
Thanks for the explanation Dmitry.
How can any app use api and auth service if cross domain is not permitted? Your V2 and SSO documentation imply that this should be possible. Apikey requests work, so the api does not appear to be restricted.
My ultimate goal is simply to obtain the user ID of the SSO-authorized user to see if the user is permitted to proceed with the form on that page (ie: if their id is in a list).
It appears that I need to use /v2/accounts/XXX/contacts/me to obtain this. But the session must be logged in as that user to make that call, so I use SSO to intercept them when they request the page (they may already be logged in, and won't be prompted). This gives the program the SSO authorization_code, which should be adequate for obtaining a valid oauth token (according to you V2/SSO authentication documentation).
The client_id/client_secret I am using is from the Settings->Security->Application_Authorization I created for this (a "User Authorization" type app, hence the OAuth client_id/secret info). Not sure why these are invalid, unless I am passing them incorrectly (do they need further encoding?). Perhaps I will try entering them in the popup just to see if they work at all.
In any case, it seems that at least the auth service (auth/token) should permit cross domain requests. My sense is that I will be able to use the API once I have an auth token.
Again, thanks for your help (and spotting the personal info)!
-
Hi Steve,
Cross domain requests to both api and auth service are not allowed.
You write that you makes requests from same origin, but in fact it is impossible, because you cant host custom content on https://oauth.wildapricot.org.
Regarding the "Authintication required" response: the only reason of getting such response is invalid client_id / client_secret. You don't have to enter any credentials in popup window, because this 401 response is expected to be processed by code, not by browser.
PS: I have removed attachments, because they contain your personal information, including your email and password. Please, change your password as soon as possible.