I have one server running Yii2 with only API endpoints, and one server with a single-page application that fetches all data from the Yii server.
I'm storing a JWT-Token in a cookie on successful login by the application, and I have a custom AuthMethod based on this.
My issue is CSRF Protection that have to be in place when cookies are used for authentication. The yii application automatically sets a _csrf cookie, but it does not do much at the moment.
As far as I can understand, I need to provide this csrf token to the frontend application on login, so that it can be stored in local storage and send in the headers for all subsequent requests.
I've implemented a check for the csrf token using Yii::$app->request->csrfTokenFromHeader and sending the token in X-CSRF-Token http header.
I'm unable to extract the same value as the one stored in the cookie inside the application. Yii::$app->request->getCsrfToken() and Yii::$app->request->csrfToken both return a different token than the one stored in the _csrf-cookie.
This is from my login endpoint:
$token = $model->setCookie(); // Sets the JWT-Token HttpOnly Cookie
$response = [
'message' => 'Login successful',
'csrf-token' => Yii::$app->request->getCsrfToken() // This does not match the _csrf cookie
];
After reading some more in the source code, I discovered that the token needs to be unmasked. This is done in Request->validateCsrfTokenInternal() that are called with Request->validateCsrfToken().
I added the following line to my AuthMethod:
if (!\Yii::$app->request->validateCsrfToken()) throw new HttpException(401, 'Invalid CSRF Token');
And that automatically checks the X-CSRF-Token-header.
I'm leaving the question in case anyone else have the same problem.
Related
UPDATE: I looked back at what I wrote up and there are too many communication layers. I am going to move forward and handle all authentication in Windows Form, generate a token there, and use it to open up the web app locally. I just need to figure out how to open a website within windows forms that can accept a model. Right now I can do, but I can only pass 1 object:
string endpointURL = "https://localhost:44318/api/login?token=" + tokenstring;
I would rather post a model, and then trigger that to open the webpage by hitting this controller endpoint in the web app
[HttpPost]
public ActionResult SecureLogin([FromBody]AuthenticateModel model)
{
return RedirectToAction("MainView", "Home", model.userID);
}
END UPDATE
I am developing a .NET Core web application. The users will login from a installed Windows Forms app I am also making. I have not done this specific way of logging in so I am looking for some advice on best practice. Here is my current process that works in development:
Desktop App accepts credentials
Once credentials are accepted a request will be sent to the web app server using a json login request
string url = "https://localhost:44318/api/TestPostData";
var httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
httpWebRequest.ContentType = "application/json; charset=utf-8";
httpWebRequest.Method = "POST";
httpWebRequest.Accept = "application/json; charset=utf-8";
using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
string loginjson = new JavaScriptSerializer().Serialize(new
{
Username = "postTest",
Password = "password"
});
streamWriter.Write(loginjson);
streamWriter.Flush();
streamWriter.Close();
var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();
Web app hears the login request, verifies with DB credentials are good
If credentials are good, the server will generate a JSON Web Token.
The token will be saved to the DB as a temporary login permission tied to that userID
The token is sent back to the local client, and put in the URL to open the web application
The web app sees this token being used to access the platform, verifies it is good and outputs the data ties to the userID
A few thoughts:
The beauty of Json web tokens is that because they are cryptographically signed, you don’t need to store them in your database. Put the username in the payload, along with other useful data like the IP, and then check their validity directly, saving yourself some db overhead.
Guard against leaked tokens by limiting the validity of the token, both in terms of an expiry time and to one IP address.
Securely hash passwords as near to the user as possible, depending on how your authentication backend works.
Json web tokens are best put in an http authorization header using the bearer mechanism.
When implementing this, stick to using implementations based on trusted and well-maintained libraries. Avoid rolling-your-own to the maximum extent possible. The devil is usually in the details.
In my angular 6 application, I’m trying to send a request to get an access token from an oauth2 provider called anilist. I’m doing it like this:
this.http.get(url, options).subscribe(result =>
{
console.log('result: ', result);
});
…where http is imported like this:
import { HttpClient } from ‘#angular/common/http';
This results in an error:
SyntaxError: Unexpected token < in JSON at position 0 at JSON.parse
This seems to be because the request is returning an html document (most likely the login page) and it obviously can’t parse it as json.
But then my question is: how does one send a request for an oauth2 token and handle the response as an html page?
Thanks.
It is a bit strange that you want to get access token via http get.
Maybe that is the problem, are you talking about this site? https://anilist.co/
https://anilist.gitbook.io/anilist-apiv2-docs/overview/oauth/authorization-code-grant
The user navigates to their site via your oAuth2 signin link
They sign in, add grant, then the browser redirects to your callback url
You change your auth code to access token via http post request. https://anilist.gitbook.io/anilist-apiv2-docs/overview/oauth/authorization-code-grant#converting-authorization-codes-to-access-tokens
I think you get an html response because
You try to get access token via http get - and here you get an unsupported method / 404 / etc error
Or you are trying to get your access token from the anilist's login page (anilist.co/api/v2/oauth/authorize) instead of the token endpoint (anilist.co/api/v2/oauth/token)
Update
It is the same case, if you are using implicit grant (because you said you are creating an angular app, which can be an SPA - therefore a public client, but in this case the access token is provided in the response) https://anilist.gitbook.io/anilist-apiv2-docs/overview/oauth/implicit-grant
I've set up a MediaWiki server on an Azure website with the PluggableAuth and OpenID Connect extensions. The latter uses the PHP OpenID Connect Basic Client library. I am an administrator in the Azure AD domain example.com, wherein I've created an application with App ID URI, sign-on URL and reply URL all set to https://wiki.azurewebsites.net/. When I navigate to the wiki, I observe the following behavior (cookie values omitted for now):
Client Request
GET https://wiki.azurewebsites.net/ HTTP/1.1
RP Request
GET https://login.windows.net/example.com/.well-known/openid-configuration
IP Response
(some response)
RP Response
HTTP/1.1 302 Moved Temporarily
Location: https://login.windows.net/{tenant_id}/oauth2/authorize?response_type=code&redirect_uri=https%3A%2F%2Fwiki.azurewebsites.net%2F&client_id={client_id}&nonce={nonce}&state={state}
Client Request
(follows redirect)
IP Response
HTTP/1.1 302 Found
Location: https://wiki.azurewebsites.net/?code={code}&state={state}&session_state={session_state}
Client Request
(follows redirect)
RP Request (also repeats #2 & #3)
POST https://login.windows.net/{tenant_id}/oauth2/token
grant_type=authorization_code&code={code}&redirect_uri=https%3A%2F%2Fwiki.azurewebsites.net%2F&client_id={client_id}&client_secret={client_secret}
IP Response
(As interpreted by MediaWiki; I don't have the full response logged at this time)
AADSTS50001: Resource identifier is not provided.
Note that if I change the OpenID PHP client to provide the 'resource' parameter in step 8, I get the following error response from AAD instead:
RP Request
POST https://login.windows.net/{tenant_id}/oauth2/token
grant_type=authorization_code&code={code}&redirect_uri=https%3A%2F%2Fwiki.azurewebsites.net%2F&resource=https%3A%2F%2Fwiki.azurewebsites.net%2F&client_id={client_id}&client_secret={client_secret}
IP Response
AADSTS90027: The client '{client_id}' and resource 'https://wiki.azurewebsites.net/' identify the same application.
(This has come up before.)
Update
I've made some progress based on #jricher's suggestions, but after working through several more errors I've hit one that I can't figure out. Once this is all done I'll submit pull requests to the affected libraries.
Here's what I've done:
I've added a second application to the example.com Azure AD domain, with the App ID URI set to mediawiki://wiki.azurewebsites.net/, as a dummy "resource". I also granted the https://wiki.azurewebsites.net/ application delegated access to this new application.
Passing in the dummy application's URI as the resource parameter in step #8, I'm now getting back the access, refresh, and ID tokens in #9!
The OpenID Connect library requires that the ID token be signed, but while Azure AD signs the access token it doesn't sign the ID token. It comes with the following properties: {"typ":"JWT","alg":"none"}. So I had to modify the library to allow the caller to specify that unsigned ID tokens are considered "verified". Grrr.
Okay, next it turns out that the claims can't be verified because the OpenID Provider URL I specified and the issuer URL returned in the token are different. (Seriously?!) So, the provider has to be specified as https://sts.windows.net/{tenant_id}/, and then that works.
Next, I found that I hadn't run the MediaWiki DB upgrade script for the OpenID Connect extension yet. Thankfully that was a quick fix.
After that, I am now left with (what I hope is) the final problem of trying to get the user info from AAD's OpenID Connect UserInfo endpoint. I'll give that its own section.
Can't get the user info [Updated]
This is where I am stuck now. After step #9, following one or two intermediate requests to get metadata and keys for verifying the token, the following occurs:
RP Request:
(Updated to use GET with Authorization: Bearer header, per MSDN and the spec.)
GET https://login.windows.net/{tenant_id}/openid/userinfo
Authorization: Bearer {access_token}
IP Response:
400 Bad Request
AADSTS50063: Credential parsing failed. AADSTS90010: JWT tokens cannot be used with the UserInfo endpoint.
(If I change #10 to be either a POST request, with access_token in the body, or a GET request with access_token in the query string, AAD returns the error: AADSTS70000: Authentication failed. UserInfo token is not valid. The same occurs if I use the value of the id_token in place of the access_token value that I received.)
Help?
Update
I'm still hoping someone can shed light on the final issue (the UserInfo endpoint not accepting the bearer token), but I may split that out into a separate question. In the meantime, I'm adding some workarounds to the libraries (PRs coming soon) so that the claims which are already being returned in the bearer token can be used instead of making the call to the UserInfo endpoint. Many thanks to everyone who's helped out with this.
There's also a nagging part of me that wonders if the whole thing would not have been simpler with the OpenID Connect Basic Profile. I assume there's a reason why that was not implemented by the MediaWiki extension.
Update 2
I just came across a new post from Vittorio Bertocci that includes this helpful hint:
...in this request the application is asking for a token for itself! In Azure AD this is possible only if the requested token is an id_token...
This suggests that just changing the token request type in step 8 from authorization_code to id_token could remove the need for the non-standard resource parameter and also make the ugly second AAD application unnecessary. Still a hack, but it feels like much less of one.
Justin is right. For authorization code grant flow, your must specify the resource parameter in either the authorization request or the token request.
Use &resource=https%3A%2F%2Fgraph.windows.net%2F to get an access token for the Azure AD Graph API.
Use &resource=https%3A%2F%2Fmanagement.core.windows.net%2F to get a token for the Azure Service Management APIs.
...
Hope this helps
Microsoft's implementation of OpenID Connect (and OAuth2) has a known bug where it requires the resource parameter to be sent by the client. This is an MS-specific parameter and requiring it unfortunately breaks compatibility with pretty much every major OAuth2 and OpenID Connect library out there. I know that MS is aware of the issue (I've been attempting to do interoperability testing with their team for quite a while now), but I don't know of any plans to fix the problem.
So in the mean time, your only real path is to hack your client software so that it sends a resource parameter that the AS will accept. It looks like you managed to make it send the parameter, but didn't send a value that it liked.
I had issues getting this running on Azure, even though I got something working locally. Since I was trying to setup a private wiki anyway, I ended up enabling Azure AD protection for the whole site by turning on:
All Settings -> Features -> Authentication / Authorization
From within the website in https://portal.azure.com
This made it so you had to authenticate to Azure-AD before you saw any page of the site. Once you were authenticated a bunch of HTTP Headers are set for the application with your username, including REMOTE_USER. As a result I used the following plugin to automatically log the already authenticated user into Azure:
https://www.mediawiki.org/wiki/Extension:Auth_remoteuser
I'm creating a system with a JavaScript client that will communicate with the server over REST (HTTP)[JSON].
I am using role-based access control to manage the calls.
Example: [explicit URL will stay the same]
Anonymous -> request \
Server -> route to login form: \login\
User (now with cookie!) -> request \
if (user->role == "manager") return "\manager-homepage\";
else return "\homepage\";
Since REST is stateless how would I go about managing this use-case?
Do I send the cookie with each request, and the returned HTTP status codes will tell the JS where to route?
[Which would be rather inefficient + open to MITM attacks]
Can you not use a standard authentication scheme, such as http digest?
Example: [from Wikipedia page]
The client asks for a page that requires authentication but does not provide a username and password. Typically this is because the user simply entered the address or followed a link to the page.
The server responds with the 401 "client-error" response code, providing the authentication realm and a randomly-generated, single-use value called a nonce.
At this point, the browser will present the authentication realm (typically a description of the computer or system being accessed) to the user and prompt for a username and password. The user may decide to cancel at this point.
Once a username and password have been supplied, the client re-sends the same request but adds an authentication header that includes the response code.
In this example, the server accepts the authentication and the page is returned. If the username is invalid and/or the password is incorrect, the server might return the "401" response code and the client would prompt the user again.
Note: A client may already have the required username and password without needing to prompt the user, e.g. if they have previously been stored by a web browser.
See also this answer to a very similar question: REST and authentication variants
Depending on your desired security level, you could serve the whole thing over ssl. That will prevent mitm attacks.
I have a web application for iPhone, which will ultimately run within a PhoneGap application - but for now I'm running it in Safari.
The application needs to access tweets from Twitter friends, including private tweets. So I've implemented OAuth using the Scribe library. I successfully bounce users to Twitter, have them authenticate, then bounce back.
At this point the web app has oAuth credentials (key and token) which it persists locally. From here on I'd like it to user the Twitter statuses/user_timeline.json method to grab tweets for a particular user. I have the application using JSONP requests to do this with unprotected tweets successfully; when it accesses the timeline of a private Twitter feed, an HTTP basic authentication dialog appears in the app.
I believe that I need to provide the OAuth credentials to Twitter, so that my web application can identify and authenticate itself. Twitter recommends doing so through the addition of an HTTP Authorization header, but as I'm using JSONP for the request I don't think this is an option for me. Am I right in assuming this?
My options therefore appear to either be putting the oAuth credentials as query-string parameters (which Twitter recommends against, but documentation suggests still supports); or proxying all the Tweets through an intermediate server. I'd rather avoid the latter.
I access the Twitter API using URLs of the form
http://api.twitter.com/1/statuses/user_timeline.json?user_id=29191439&oauth_nonce=XXXXXXXXXXX&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1272323042&oauth_consumer_key=XXXXXXXXXX&oauth_signature=XXXXXXXXXX&oauth_version=1.0
When user_id is a public user, this works fine. When user_id is a private user, I get that HTTP Basic Auth dialog. Any idea what I'm doing wrong? I'm hoping it's something embarrassingly simple like "forgetting an important parameter"...
The oAuth stanza needs to be exact, as per http://dev.twitter.com/pages/auth#auth-request - I ended up building an Authorization: header that I could first check with curl.
I built it using the really helpful interactive request checker at http://hueniverse.com/2008/10/beginners-guide-to-oauth-part-iv-signing-requests/
Here's a friends API request for a protected user:
curl -v -H 'Authorization: OAuth realm="https://api.twitter.com/1/friends/ids.json", oauth_consumer_key="XXXXXXXXXXXXXXXX", oauth_token="XXXXXXXXXXXXXXXX", oauth_nonce="XXXXXXXXXXXXXXXX", oauth_timestamp="1300728665", oauth_signature_method="HMAC-SHA1", oauth_version="1.0", oauth_signature="XXXXXXXXXXXXXXXX%3D"' https://api.twitter.com/1/friends/ids.json?user_id=254723679
It's worth re-iterating that as you've tried to do, instead of setting the Authorization header via e.g. jquery's beforeSend function, that for cross-domain JSONP requests (which can't add HTTP headers) you can make oAuth requests by putting all the relevant key/value pairs in the GET request. This should hopefully help out various other questioners, e.g
Set Headers with jQuery.ajax and JSONP?
Modify HTTP Headers for a JSONP request
Using only JQuery to update Twitter (OAuth)
Your request looks like it has a couple of problems; it's missing the user's oauth_token plus the oauth_signature doesn't look like it has been base64 encoded (because it's missing a hex encoded = or ==, %3 or %3D%3D respectively).
Here's my GET equivalent using oAuth encoded querystring params, which you can use in a cross-domain JSONP call:
https://api.twitter.com/1/friends/ids.json?user_id=254723679&realm=https://api.twitter.com/1/friends/ids.json&oauth_consumer_key=XXXXXXXXXXXXXXXX&oauth_token=XXXXXXXXXXXXXXXX&oauth_nonce=XXXXXXXXXXXXXXXX&oauth_timestamp=1300728665&oauth_signature_method=HMAC-SHA1&oauth_version=1.0&oauth_signature=XXXXXXXXXXXXXXXX%3D
I was struggling with similar problem of making JSONP requests from Jquery, the above answer helped just to add what I did to achieve my solution.
I am doing server to server oauth and then I send oauth token, secret, consumer key and secret (this is temporary solution by the time we put a proxy to protect consumer secret). You can replace this to token acquiring code at client.
Oauth.js and Sha1.js download link!
Once signature is generated.
Now there are 2 problems:
JSONP header cannot be edited
Signed arguments which needs to be sent as part of oauth have problem with callback=? (a regular way of using JSONP).
As above answer says 1 cannot be done.
Also, callback=? won't work as the parameter list has to be signed and while sending the request to remote server Jquery replace callback=? to some name like callback=Jquery1232453234. So a named handler has to be used.
function my_twitter_resp_handler(data){
console.log(JSON.stringify(data));
}
and getJSON did not work with named function handler, so I used
var accessor = {
consumerSecret: XXXXXXXXXXXXXXXXXXXXXX,
tokenSecret : XXXXXXXXXXXXXXXXXXXXXX
};
var message = { action: "https://api.twitter.com/1/statuses/home_timeline.json",
method: "GET",
parameters: []
};
message.parameters.push(['realm', "https://api.twitter.com/1/statuses/home_timeline.json"]);
message.parameters.push(['oauth_version', '1.0']);
message.parameters.push(['oauth_signature_method', 'HMAC-SHA1']);
message.parameters.push(['oauth_consumer_key', XXXXXXXXXXXXXXXX]);
message.parameters.push(['oauth_token', XXXXXXXXXXXXXXX]);
message.parameters.push(['callback', 'my_twitter_resp_handler']);
OAuth.completeRequest(message, accessor);
var parameterMap = OAuth.getParameterMap(message.parameters);
Create url with base url and key value pairs from parameterMap
jQuery.ajax({
url: url,
dataType: "jsonp",
type: "GET",
});