How to handle authentication with TIdHTTP - c++builder
I'm using C++Builder XE6 and TIdHTTP to communicate with a REST server in a Windows application.
I need some advice on how to handle authentication.
Given the following code:
#include <IdHTTP.hpp>
#include <IdAuthenticationDigest.hpp>
HTTP = new TIdHTTP(NULL);
HTTP->Request->Username = Username;
HTTP->Request->Password = Password;
HTTP->Request->BasicAuthentication = UseBasicAuthentication;
HTTP->OnAuthorization = AuthRequired;
The variables Username (string), Password (string) and UseBasicAuthentication (bool) are user-configurable parameters.
I don't know in advance if the REST server requires authentication or not, nor which type of authentication it supports (Basic or Digest).
AFAIK, the Username and Password members of TIdHTTPRequest are used in the first try to authenticate with the server.
If the initial authentication fails, the OnAuthorization event handler is triggered to obtain new credentials.
Does TIdHTTP only supply the credentials in response to a 401 response code from the server, or are they always included in the request?
Also, is there a standard dialog box that can be used to prompt the user for credentials?
Update
Also need to set the following:
HTTP->HTTPOptions = HTTP->HTTPOptions << hoInProcessAuth;
Update 2
CredUIPromptForCredentials() can be used to show a credentials dialog box on Windows:
#include <wincred.h>
CREDUI_INFO cui;
cui.cbSize = sizeof(cui);
cui.hwndParent = NULL;
cui.pszMessageText = _T("Your message here");
cui.pszCaptionText = Application->Title.c_str();
cui.hbmBanner = NULL;
TCHAR pszUsername[CREDUI_MAX_USERNAME_LENGTH + 1] = _T("Username");
TCHAR pszPassword[CREDUI_MAX_PASSWORD_LENGTH + 1] = _T("Password");
BOOL fSave = false;
if (CredUIPromptForCredentials(&cui, _T("https://www.stackoverflow.com"), NULL, 0, pszUsername, ARRAYSIZE(pszUsername), pszPassword, ARRAYSIZE(pszPassword), &fSave, CREDUI_FLAGS_DO_NOT_PERSIST | CREDUI_FLAGS_SHOW_SAVE_CHECK_BOX | CREDUI_FLAGS_ALWAYS_SHOW_UI | CREDUI_FLAGS_GENERIC_CREDENTIALS) == NO_ERROR)
...
The pszUsername and pszPassword parameters return the entered credentials as plaintext.
The state of the 'Remember my credentials' checkbox is returned in the fSave parameter.
Update 3
I tried the following to test Basic Authentication:
#include <IdHTTP.hpp>
#include <IdAllAuthentications.hpp>
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TIdHTTP* HTTP = new TIdHTTP(NULL);
try
{
HTTP->HTTPOptions = HTTP->HTTPOptions << hoInProcessAuth << hoNoProtocolErrorException;
HTTP->Request->BasicAuthentication = false;
HTTP->MaxAuthRetries = 10;
HTTP->OnAuthorization = AuthRequired;
HTTP->Get("http://httpbin.org/basic-auth/user/pass");
Memo->Lines->Add(IntToStr(HTTP->ResponseCode));
}
__finally
{
delete HTTP;
}
}
void __fastcall TForm1::AuthRequired (TObject *Sender, TIdAuthentication *Authentication, bool &Handled)
{
TIdHTTP* HTTP = dynamic_cast<TIdHTTP*>(Sender);
String Username = Authentication->Username;
String Password = Authentication->Password;
String Server = HTTP->URL->Protocol + "://" + HTTP->URL->Host;
Handled = PromptForCredentials(Application->Title, "Enter credentials for " + Server + ".", Server, Username, Password);
Authentication->Username = Username;
Authentication->Password = Password;
}
I wrapped the code in Update 2 in a function called PromptForCredentials(), which returns true when the user chose OK.
TIdHTTP triggers the OnAuthorization event only once.
If I enter the correct credentials, I get a 200 response, which is correct.
If I enter incorrect credentials, I get a 401 response without triggering the OnAuthorization event again.
After the HTTP->Get() call, the value of HTTP->Request->BasicAuthentication changed to true, which is correct.
Shouldn't TIdHTTP keep triggering OnAuthorization until either a 200 response, Handled is set to false or HTTP->MaxAuthRetries is reached?
PS: My Indy version is 10.6.0.5122.
Update 4
To test Digest Authentication, I changed the following line in the code listed under Update 3:
HTTP->Get("http://httpbin.org/digest-auth/auth/user/pass");
OnAuthorization is also triggered only once. This time, HTTP->Request->BasicAuthentication remains false, which is correct.
Update 5
I've set HTTP->MaxAuthRetries = 3 and added file logging:
HTTP->Intercept = new TIdLogFile(HTTP);
static_cast<TIdLogFile*>(HTTP->Intercept)->Filename = "Project1.log";
static_cast<TIdLogFile*>(HTTP->Intercept)->Active = true;
If I enter incorrect credentials (Username = test, Password = test) in the dialog box when OnAuthorization is triggered, the logs show that TIdHTTP replies to the 401 response 3 times with the same credentials. The OnAuthorization is not triggered again after the first one, for both Basic Autentication and Digest Authentication.
The log for Basic Authentication:
Stat Connected.
Sent 31-03-2022 08:58:55: GET /basic-auth/user/pass HTTP/1.1<EOL>Host: httpbin.org<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL><EOL>
Recv 31-03-2022 08:58:55: HTTP/1.1 401 UNAUTHORIZED<EOL>Date: Thu, 31 Mar 2022 06:58:55 GMT<EOL>Content-Length: 0<EOL>Connection: keep-alive<EOL>Server: gunicorn/19.9.0<EOL>WWW-Authenticate: Basic realm="Fake Realm"<EOL>Access-Control-Allow-Origin: *<EOL>Access-Control-Allow-Credentials: true<EOL><EOL>
Sent 31-03-2022 08:59:03: GET /basic-auth/user/pass HTTP/1.1<EOL>Host: httpbin.org<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL>Authorization: Basic dGVzdDp0ZXN0<EOL><EOL>
Recv 31-03-2022 08:59:03: HTTP/1.1 401 UNAUTHORIZED<EOL>Date: Thu, 31 Mar 2022 06:59:03 GMT<EOL>Content-Length: 0<EOL>Connection: keep-alive<EOL>Server: gunicorn/19.9.0<EOL>WWW-Authenticate: Basic realm="Fake Realm"<EOL>Access-Control-Allow-Origin: *<EOL>Access-Control-Allow-Credentials: true<EOL><EOL>
Sent 31-03-2022 08:59:03: GET /basic-auth/user/pass HTTP/1.1<EOL>Host: httpbin.org<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL>Authorization: Basic dGVzdDp0ZXN0<EOL><EOL>
Recv 31-03-2022 08:59:03: HTTP/1.1 401 UNAUTHORIZED<EOL>Date: Thu, 31 Mar 2022 06:59:03 GMT<EOL>Content-Length: 0<EOL>Connection: keep-alive<EOL>Server: gunicorn/19.9.0<EOL>WWW-Authenticate: Basic realm="Fake Realm"<EOL>Access-Control-Allow-Origin: *<EOL>Access-Control-Allow-Credentials: true<EOL><EOL>
Sent 31-03-2022 08:59:03: GET /basic-auth/user/pass HTTP/1.1<EOL>Host: httpbin.org<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL>Authorization: Basic dGVzdDp0ZXN0<EOL><EOL>
Recv 31-03-2022 08:59:03: HTTP/1.1 401 UNAUTHORIZED<EOL>Date: Thu, 31 Mar 2022 06:59:03 GMT<EOL>Content-Length: 0<EOL>Connection: keep-alive<EOL>Server: gunicorn/19.9.0<EOL>WWW-Authenticate: Basic realm="Fake Realm"<EOL>Access-Control-Allow-Origin: *<EOL>Access-Control-Allow-Credentials: true<EOL><EOL>
Stat Disconnected.
Stat Disconnected.
Note that dGVzdDp0ZXN0 (Base64) decodes to test:test.
The log for Digest Authentication:
Stat Connected.
Sent 31-03-2022 09:28:41: GET /digest-auth/auth/user/pass HTTP/1.1<EOL>Host: httpbin.org<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL><EOL>
Recv 31-03-2022 09:28:41: HTTP/1.1 401 UNAUTHORIZED<EOL>Date: Thu, 31 Mar 2022 07:28:41 GMT<EOL>Content-Type: text/html; charset=utf-8<EOL>Content-Length: 0<EOL>Connection: keep-alive<EOL>Server: gunicorn/19.9.0<EOL>WWW-Authenticate: Digest realm="me#kennethreitz.com", nonce="83f09ed264f5704d1cfc630d90b3091d", qop="auth", opaque="d075bef19d3e723ddf38e585e4c15142", algorithm=MD5, stale=FALSE<EOL>Set-Cookie: stale_after=never; Path=/<EOL>Set-Cookie: fake=fake_value; Path=/<EOL>Access-Control-Allow-Origin: *<EOL>Access-Control-Allow-Credentials: true<EOL><EOL>
Sent 31-03-2022 09:28:46: GET /digest-auth/auth/user/pass HTTP/1.1<EOL>Host: httpbin.org<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL>Authorization: Digest username="test", realm="me#kennethreitz.com", nonce="83f09ed264f5704d1cfc630d90b3091d", algorithm="MD5", uri="/digest-auth/auth/user/pass", qop="auth", nc=00000001, cnonce="f73bb234744d902a6909b0d511c6eab4", response="a7e13d327c9c13a446413530cfc44fc9", opaque="d075bef19d3e723ddf38e585e4c15142"<EOL>Cookie: fake=fake_value; stale_after=never<EOL><EOL>
Recv 31-03-2022 09:28:47: HTTP/1.1 401 UNAUTHORIZED<EOL>Date: Thu, 31 Mar 2022 07:28:47 GMT<EOL>Content-Type: text/html; charset=utf-8<EOL>Content-Length: 0<EOL>Connection: keep-alive<EOL>Server: gunicorn/19.9.0<EOL>WWW-Authenticate: Digest realm="me#kennethreitz.com", nonce="be0945b8e925b386b3f493058d757aeb", qop="auth", opaque="f0301998fadceaf2d562e3b4934da438", algorithm=MD5, stale=FALSE<EOL>Set-Cookie: stale_after=never; Path=/<EOL>Set-Cookie: last_nonce=83f09ed264f5704d1cfc630d90b3091d; Path=/<EOL>Set-Cookie: fake=fake_value; Path=/<EOL>Access-Control-Allow-Origin: *<EOL>Access-Control-Allow-Credentials: true<EOL><EOL>
Sent 31-03-2022 09:28:47: GET /digest-auth/auth/user/pass HTTP/1.1<EOL>Host: httpbin.org<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL>Authorization: Digest username="test", realm="me#kennethreitz.com", nonce="be0945b8e925b386b3f493058d757aeb", algorithm="MD5", uri="/digest-auth/auth/user/pass", qop="auth", nc=00000001, cnonce="fe65a7188cc197c3ee68c1e2840bab9a", response="2cfce7fc3c9099430841cab1b10eb706", opaque="f0301998fadceaf2d562e3b4934da438"<EOL>Cookie: fake=fake_value; stale_after=never; last_nonce=83f09ed264f5704d1cfc630d90b3091d<EOL><EOL>
Recv 31-03-2022 09:28:47: HTTP/1.1 401 UNAUTHORIZED<EOL>Date: Thu, 31 Mar 2022 07:28:47 GMT<EOL>Content-Type: text/html; charset=utf-8<EOL>Content-Length: 0<EOL>Connection: keep-alive<EOL>Server: gunicorn/19.9.0<EOL>WWW-Authenticate: Digest realm="me#kennethreitz.com", nonce="6cffc5d099ca89fffc766509726ecd1e", qop="auth", opaque="dea9f785474babae87a69b120be54038", algorithm=MD5, stale=FALSE<EOL>Set-Cookie: stale_after=never; Path=/<EOL>Set-Cookie: last_nonce=be0945b8e925b386b3f493058d757aeb; Path=/<EOL>Set-Cookie: fake=fake_value; Path=/<EOL>Access-Control-Allow-Origin: *<EOL>Access-Control-Allow-Credentials: true<EOL><EOL>
Sent 31-03-2022 09:28:47: GET /digest-auth/auth/user/pass HTTP/1.1<EOL>Host: httpbin.org<EOL>Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8<EOL>Accept-Encoding: identity<EOL>User-Agent: Mozilla/3.0 (compatible; Indy Library)<EOL>Authorization: Digest username="test", realm="me#kennethreitz.com", nonce="6cffc5d099ca89fffc766509726ecd1e", algorithm="MD5", uri="/digest-auth/auth/user/pass", qop="auth", nc=00000001, cnonce="fe65a7188cc197c3ee68c1e2840bab9a", response="e425bf56ca44430aa8f864fb9e4b10a7", opaque="dea9f785474babae87a69b120be54038"<EOL>Cookie: fake=fake_value; stale_after=never; last_nonce=be0945b8e925b386b3f493058d757aeb<EOL><EOL>
Recv 31-03-2022 09:28:47: HTTP/1.1 401 UNAUTHORIZED<EOL>Date: Thu, 31 Mar 2022 07:28:47 GMT<EOL>Content-Type: text/html; charset=utf-8<EOL>Content-Length: 0<EOL>Connection: keep-alive<EOL>Server: gunicorn/19.9.0<EOL>WWW-Authenticate: Digest realm="me#kennethreitz.com", nonce="b448581a59e0fec2df8f3f9f8340721b", qop="auth", opaque="049d8958d842b80beee17b21d04e5b7e", algorithm=MD5, stale=FALSE<EOL>Set-Cookie: stale_after=never; Path=/<EOL>Set-Cookie: last_nonce=6cffc5d099ca89fffc766509726ecd1e; Path=/<EOL>Set-Cookie: fake=fake_value; Path=/<EOL>Access-Control-Allow-Origin: *<EOL>Access-Control-Allow-Credentials: true<EOL><EOL>
Stat Disconnected.
Stat Disconnected.
If I set HTTP->Request->Username = "test" and HTTP->Request->Password = "test" prior to the call to HTTP->Get(...), the OnAuthorization event handler is never triggered and the logs also show that TIdHTTP retries 3 times with the incorrect credentials.
I don't know in advance if the REST server requires authentication or not, nor which type of authentication it supports (Basic or Digest).
TIdHTTP natively supports Basic when Request->Authentication is NULL and Request->BasicAuthentication is true.
TIdHTTP also supports Digest, however you have to enable it manually first, by adding one of the following statements to your code:
#include <IdAuthenticationDigest.hpp>
#include <IdAllAuthentications.hpp>
(#pragma link is handled for you)
Internally, TIdHTTP will then pick the appropriate TIdAuthentication-derived class based on what the server asks for in a 401 response.
AFAIK, the Username and Password members of TIdHTTPRequest are used in the first try to authenticate with the server. If the initial authentication fails, the OnAuthorization event handler is triggered to obtain new credentials.
Correct.
Does TIdHTTP only supply the credentials in response to a 401 response code from the server, or are they always included in the request?
That depends.
If Request->Authentication is assigned a TIdAuthentication-derived object, it is queried for pending credentials, and if any are given then they are sent to the server. For example, in multi-step authentications like NTLM, etc.
Otherwise, if Request->Authentication is NULL and Request->BasicAuthentication is true, Request->Authentication is set to TIdBasicAuthentication, and then Basic credentials are sent to the server (even if the UserName and Password are blank! See #403 in Indy's issue tracker).
Otherwise, no credentials are sent to the server, unless you provide your own credentials in the Request->CustomHeaders property.
UPDATE
TIdHTTP triggers the OnAuthorization event only once. If I enter the correct credentials, I get a 200 response, which is correct. If I enter incorrect credentials, I get a 401 response without triggering the OnAuthorization event again.
TIdHTTP is supposed to trigger the OnAuthorization event each time it receives a 401 response, unless either:
TIdHTTP.AuthRetries reaches TIdHTTP.MaxAuthRetries
the TIdHTTP.OnSelectAuthorization event sets the AuthenticationClass parameter to nil
However, if neither of those conditions are met, the OnAuthorization event is triggered only when the current Request->Authentication object returns wnAskTheProgram from its Next() method, which is called each time 401 is received (after the above conditions are checked).
In the case of TIdBasicAuthentication, its Next() method returns wnAskTheProgram only when the current Username is blank. This is probably a bug that needs to be fixed (I have opened a ticket in Indy's issue tracker for you). The failed Username/Password should be reset on each 401 response. And there is actually code to that effect in IdHTTP.pas, but it is currently commented out because apparently it breaks multi-step authentications, like SSPI/NTLM.
After the HTTP->Get() call, the value of HTTP->Request->BasicAuthentication changed to true, which is correct.
It really shouldn't. But after OnAuthorization is triggered the 1st time, BasicAuthentication is hard-coded to true. I don't know why that was ever added to TIdHTTP.
Shouldn't TIdHTTP keep triggering OnAuthorization until either a 200 response, Handled is set to false or HTTP->MaxAuthRetries is reached?
It should, yes.
If I enter incorrect credentials (Username = test, Password = test) in the dialog box when OnAuthorization is triggered, the logs show that TIdHTTP replies to the 401 response 3 times with the same credentials. The OnAuthorization is not triggered again after the first one, for both Basic Autentication and Digest Authentication.
They are both affected by the same underlying issue - that the current Username is not being cleared after each failed attempt.
Related
Delphi & Indy httpServer how stop sending 400 bad Request
I get data from a device that expects him back ACK (HTTP1/1 200 OK). My httpserver after receiving header is automatically returned 400 Bad Request (i see on WareShark). Perhaps the device is not properly built his request. How do I stop server does not return an error? So I will be able to continue communication with device. thanks LOG: 192.168.1.141:57565 Stat Connected. HTTP Connect 192.168.1.141 LOG: 192.168.1.141:57565 Recv 10.4.2017 г. 00:18:43: POST / HTTP/1.1<EOL> LOG: 192.168.1.141:57565 Recv 10.4.2017 г. 00:18:43: Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/x-shockwave-flash, text/*, */*<EOL>Accept-Language: en-us<EOL>Content-Type: application/x-www-form-urlencoded<EOL>Accept-Encoding: gzip, deflate<EOL>User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705)<EOL>Content-Length: 579<EOL>Connection: Keep-Alive<EOL><EOL><?xml version="1.0"?><EOL><Metrics SiteId="BG-001" Sitename="office Pazardjik"><EOL><Properties><EOL><MacAddress>00:b0:9d:7f:b7:b2</MacAddress><EOL><IpAddress>0.0.0.0</IpAddress><EOL><Timezone>2</Timezone><EOL><DST>1</DST><EOL><DeviceType>0</DeviceType><EOL><SerialNumber>8370098</SerialNumber><EOL></Properties><EOL><ReportData Interval="1"><EOL><Report Date="2017-04-06"><EOL><Object Id="0" DeviceId="BG-001-01" Devicename="Main Entrance" ObjectType="0" Name="Main Entrance"><EOL><Count StartTime="05:31:00" EndTime="05:32:00" Enters="0" Exits="0" Status="0"/><EOL></Object><EOL></Report><EOL></ReportData><EOL></Metrics><EOL> HTTP Header Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/x-shockwave-flash, text/*, */* Accept-Language: en-us Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705) Content-Length: 579 Connection: Keep-Alive LOG: 192.168.1.141:57565 Sent 10.4.2017 г. 00:18:43: HTTP/1.1 400 Bad Request<EOL>Connection: close<EOL>Content-Length: 0<EOL>Date: Sun, 09 Apr 2017 21:18:43 GMT<EOL><EOL> LOG: 192.168.1.141:57565 Stat Disconnected. LOG: 0.0.0.0:0 Stat Disconnected.
The HTTP client is sending an HTTP 1.1 request, but is not sending a required Host request header. Per RFC 2616 Section 14.23: A client MUST include a Host header field in all HTTP/1.1 request messages. If the requested URI does not include an Internet host name for the service being requested, then the Host header field MUST be given with an empty value. An HTTP/1.1 proxy MUST ensure that any request message it forwards does contain an appropriate Host header field that identifies the service being requested by the proxy. All Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad Request) status code to any HTTP/1.1 request message which lacks a Host header field. TIdHTTPServer returns a 400 response if it receives an HTTP 1.1 request without a Host header. As you can see above, the 400 response is mandatory by the HTTP 1.1 spec. I suggest you contact the device manufacturer and report a bug about the missing Host header, maybe they can release a firmware update. In the meantime, you can use the TIdHTTPServer.OnHeadersAvailable event to insert a dummy Host header if it is missing: procedure httpServerHeadersAvailable(AContext: TIdContext; const AUri: string; AHeaders: TIdHeaderList; var VContinueProcessing: Boolean); begin if AHeaders.Values['Host'] = '' then AHeaders.Values['Host'] = 'myserver'; VContinueProcessing := true; end; As for your log, it shows an Access Violation occurring in your OnCommandGet event handler, due to a nil pointer being accessed. That event is not triggered in the case when the client Host header is missing. So you clearly have a second issue in your code, not related to this issue.
OAuth token submitted with the request can not be parsed
I'm trying to implement a client that imports the events that a user has in Office 365 so that I can easily display them in the company's application. I managed to get the user to authenticate with his / her Office 365 account and to approve my application and to also get an AccessToken, but when I try to use the token to retrieve the events from the API, I get a 401 HTTP error code, no body and in the headers I have this: Content-Length →0 Date →Thu, 17 Mar 2016 08:56:00 GMT Server →Microsoft-IIS/8.0 WWW-Authenticate →Bearer client_id="00000002-0000-0ff1-ce00-000000000000", trusted_issuers="00000001-0000-0000-c000-000000000000#*", token_types="app_asserted_user_v1 service_asserted_app_v1", authorization_uri="https://login.windows.net/common/oauth2/authorize", error="invalid_token",Basic Realm="",Basic Realm="" X-BEServer →DB4PR06MB522 X-BackEndHttpStatus →401 X-CalculatedBETarget →DB4PR06MB522.eurprd06.prod.outlook.com X-DiagInfo →DB4PR06MB522 X-FEServer →AM3PR06CA022 X-Powered-By →ASP.NET request-id →de1963bc-36df-4473-81f6-66ec37e8b415 x-ms-diagnostics →2000001;reason="OAuth token submitted with the request can not be parsed.";error_category="invalid_token" The token I get from https://login.microsoftonline.com/common/oauth2/token with the following body: grant_type=authorization_code redirect_uri=https://example.com/redirect-uri client_id=XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX client_secret=[my-client-secret] code=[code-received-from-user-auth] The token I receive from the above call, I send through the Authorization header to https://outlook.office.com/api/v2.0/me/events like so: Authorization: Bearer [access-token] The response status I get from this call is 401 Unauthorized, I get an empty body and the headers are like so: Content-Length →0 Date →Thu, 17 Mar 2016 08:56:00 GMT Server →Microsoft-IIS/8.0 WWW-Authenticate →Bearer client_id="00000002-0000-0ff1-ce00-000000000000", trusted_issuers="00000001-0000-0000-c000-000000000000#*", token_types="app_asserted_user_v1 service_asserted_app_v1", authorization_uri="https://login.windows.net/common/oauth2/authorize", error="invalid_token",Basic Realm="",Basic Realm="" X-BEServer →DB4PR06MB522 X-BackEndHttpStatus →401 X-CalculatedBETarget →DB4PR06MB522.eurprd06.prod.outlook.com X-DiagInfo →DB4PR06MB522 X-FEServer →AM3PR06CA022 X-Powered-By →ASP.NET request-id →de1963bc-36df-4473-81f6-66ec37e8b415 x-ms-diagnostics →2000001;reason="OAuth token submitted with the request can not be parsed.";error_category="invalid_token" Can you please tell me what I'm doing wrong?
I managed to fix the problem I had. For future reference, the problem was that I wasn't telling the https://login.microsoftonline.com/common/oauth2/token endpoint what I needed the token for. I had to provide a resource parameter with the base url of the resource I was going to interogate after. In my case, it was https://outlook.office365.com.
Why is action result cached?
I have an action that generates a password reset link and emails it to the user public ActionResult SendResetPasswordEmail(string userName) { var webUser = LoadUser(userName); if (webUser != null) { var token = WebSecurity.GeneratePasswordResetToken(webUser.UserName); emailSender.SendPasswordResetEmail(webUser, token, resetAction); return new HttpStatusCodeResult(HttpStatusCode.OK); } return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "No user found with username: " + userName); } The first time I call the action from the browser, I get an HTTP 200 response (and hit my breakpoint in the action). The second time I call the action from the browser, I get an HTTP 304 response indicating that the content is unchanged. There are no [OutputCache] attributes anywhere in the source file (not on the class or the action). What is causing the web server to decide that the content is unchanged and return the HTTP 304? I'm aware of a work-around https://stackoverflow.com/a/18620970/141172 I'm interested in understanding the root cause for the HTTP 304 response. Update Headers on first request: Request Headers Request GET /Companies/SendResetPasswordEmail/?userName=ej HTTP/1.1 X-Requested-With XMLHttpRequest Accept */* Referer http://local:6797/Companies Accept-Language en-US Accept-Encoding gzip, deflate User-Agent Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0) Host localhost:6797 DNT 1 Connection Keep-Alive Cookie __RequestVerificationToken=sNOBS6qz32LtnJpLWgHHELhaE44DfIVE1LSMUgjzHjcwsvxlUFa4lOSyA5QeB8keLXYL08Psjg29CRI7W73uHLJy6A81; .ASPXAUTH=DAF8AF47E955F723EE9438866BE1B4BFBF91BA01912EF087824F03581DBCA05A4AECA01373FAF40DF0C4D5C17F17DEFA2F85C1B702988B7E0F750BFE19566FC711C7D6BD81D8F0B0ABD68AF5B3D9BA032286361F; ASP.NET_SessionId=5e2gcvkc2p3rji25z5emyqzd; HelixPlugins1.0=IEPlugin1.0 Response Headers Response HTTP/1.1 200 OK Server ASP.NET Development Server/11.0.0.0 Date Thu, 03 Apr 2014 23:29:02 GMT Cache-Control private, s-maxage=0 Content-Length 0 Connection Close NOTE: I changed localhost to local in the above because StackOverflow does not allow links containing localhost to be posted :-) The browser is Internet Explorer 10.
IE caches ajax responses by default, you need to explicitly tell it not to do any ajax caching by setting your ajax object's cache property to false. Browsers such as Chrome automatically append a random token to your request to make it unique.
Box oauth2: Invalid grant_type parameter or parameter missing
I don't know what I do wrong, but everytime I tried to obtain the token (after user authentication of course), the result is always Invalid grant_type parameter or parameter missing Possibly related to Box API always returns invalid grant_type parameter on obtaining access token Here is my fiddler result: POST https://api.box.com/oauth2/token HTTP/1.1 Host: api.box.com Content-Length: 157 Expect: 100-continue Connection: Keep-Alive grant_type=authorization_code&code=nnqtYcoik7cjtHQYyn3Af8uk4LG3rYYh&client_id=[myclientId]&client_secret=[mysecret] Result: HTTP/1.1 400 Bad Request Server: nginx Date: Thu, 07 Mar 2013 11:18:36 GMT Content-Type: application/json Connection: keep-alive Set-Cookie: box_visitor_id=5138778bf12a01.27393131; expires=Fri, 07-Mar-2014 11:18:35 GMT; path=/; domain=.box.com Set-Cookie: country_code=US; expires=Mon, 06-May-2013 11:18:36 GMT; path=/ Cache-Control: no-store Content-Length: 99 {"error":"invalid_request","error_description":"Invalid grant_type parameter or parameter missing"} Even following the curl example gives the same error. Any help would be appreciated. Edit: tried with additional redirect_uri params but still the same error POST https://api.box.com/oauth2/token HTTP/1.1 Content-Type: application/json; charset=UTF-8 Host: api.box.com Content-Length: 187 Expect: 100-continue Connection: Keep-Alive grant_type=authorization_code&code=R3JxS7UPm8Gjc0y7YLj9qxifdzBYzLOZ&client_id=*****&client_secret=*****&redirect_uri=http://localhost Result: HTTP/1.1 400 Bad Request Server: nginx Date: Sat, 09 Mar 2013 00:46:38 GMT Content-Type: application/json Connection: keep-alive Set-Cookie: box_visitor_id=513a866ec5cfe0.48604831; expires=Sun, 09-Mar-2014 00:46:38 GMT; path=/; domain=.box.com Set-Cookie: country_code=US; expires=Wed, 08-May-2013 00:46:38 GMT; path=/ Cache-Control: no-store Content-Length: 99 {"error":"invalid_request","error_description":"Invalid grant_type parameter or parameter missing"}
Looks like Box requires a correct Content-Type: application/x-www-form-urlencoded request header in addition to properly URL encoding the parameters. The same seems to apply to refresh and revoke requests. Also, per RFC 6749, the redirect_uri is only REQUIRED, if the "redirect_uri" parameter was included in the authorization request as described in Section 4.1.1, and their values MUST be identical.
I was facing a similar issue. The problem is not with Content-Type. The issue is with the lifecycle of code you receive. One key aspect not mentioned in most places is that the code you get on redirect lasts only 30 seconds. To get the access token and refresh token, you have to make the post request in 30 seconds or less. If you fail to do that, you get the stated error. I found the info here. Below code worked for me. Keep in mind, the 30-second rule. import requests url = 'https://api.box.com/oauth2/token' data = [ ('grant_type', 'authorization_code'), ('client_id', 'YOUR_CLIENT_ID'), ('client_secret', 'YOUR_CLIENT_SECRET'), ('code', 'XXXXXX'), ] response = requests.post(url, data=data) print(response.content) Hope that helps.
You are missing the redirect URI parameter. Try: POST https://api.box.com/oauth2/token HTTP/1.1 Host: api.box.com Content-Length: 157 Expect: 100-continue Connection: Keep-Alive grant_type=authorization_code&code=nnqtYcoik7cjtHQYyn3Af8uk4LG3rYYh&client_id=[myclientId]&client_secret=[mysecret]&redirect_uri=[your-redirect-uri]
I have also face same issue implementing oauth2. I have add Content-Type: application/x-www-form-urlencoded. When I add content-type my issue solved. Check and add valid content-type.
Not sure who might need this in the future but be sure you're sending a POST request to get the access token and not trying to retrieve it by using GET or if you're testing- pasting in the address bar won't work, you need to send a POST request with the data in the BODY and not as query parameter. Also the code usually lasts for a few seconds, so you need to use it as soon as its sent back.
How to make use of jsessionid together with basic authentication
I am using JBoss 7.1 and have secured my web application with Basic authentication but I want only the first call to require the Basic authentication header, sequent calls should use the jsessionid for authentication. How to accomplish this? So far I have created a rest servlet enforcing the creation of a session with a call to request.getSession() #Path("/rest/HelloWorld") public class HelloWorld { #GET() #Produces("text/plain") public String sayHello(#Context HttpServletResponse response, #Context HttpServletRequest request) { HttpSession session = request.getSession(); return "Hello World! " + request.getUserPrincipal().getName(); } My idea was that any other calls should only require the jsessionid cookie, but when looking in fiddler I see that the first call is behaving as expected. First you get a 401 and the client is re-sending including the basic authorization header and a jsessionid is returned. On the second call the jsessionid cookie is included but I still get an 401 that triggers the client to re-send the Basic authorization header. This is the returned headers from the successful authenticated first call. HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Pragma: No-cache Cache-Control: no-cache Expires: Thu, 01 Jan 1970 01:00:00 CET Set-Cookie: JSESSIONID=AFDFl2etiUNkn-mpM+DXr3KE; Path=/Test Content-Type: text/plain Content-Length: 18 Date: Tue, 29 Jan 2013 09:12:48 GMT Hello World! test1 when I make a second call the jsessionid is included GET /Test/index.html HTTP/1.1 Host: cwl-rickard:8080 Cookie: JSESSIONID=AFDFl2etiUNkn-mpM+DXr3KE and I am getting a 401 enforcing the client to re-send the request including the basic authorization header. HTTP/1.1 401 Unauthorized Server: Apache-Coyote/1.1 Pragma: No-cache Cache-Control: no-cache Expires: Thu, 01 Jan 1970 01:00:00 CET WWW-Authenticate: Basic realm="ApplicationRealm" Content-Type: text/html;charset=utf-8 Content-Length: 958 Date: Tue, 29 Jan 2013 09:12:48 GMT Any ideas what I am missing.