I'm trying to get an OAuth access token from the GitLab CE endpoint for OAuth. I've got the code parameter fine in JS, but when I make an Ajax request to the access token page, it throws an error about the server not being CORS enabled, and the origin null not being allowed. Why is my origin null? I also looked at the gitlab documentation, it says you have to use a http client, and then it shows an example of RestClient. What is this? I've tried using it but it doesn't work. Please see below for my code:
if (getQueryVariable('code') === false) {
if (local !== false) {
window.location.replace('https://gitlab.com/oauth/authorize?client_id=####&redirect_uri=http://localhost:4000&response_type=code');
} else { //live
window.location.replace('https://gitlab.com/oauth/authorize?client_id=####&redirect_uri=http://roconnor.gitlab.io/repoViewer&response_type=code');
}
} else {
var oauthcode;
if (local !== false) {
oauthcode = $.getJSON('http://gitlab.com/oauth/token?client_id=####&client_secret=####&code=' + getQueryVariable('code') + '&grant_type=AUTHORIZATION_CODE&redirect_uri=http://localhost:4000');
var parameters = 'client_id=####&client_secret=####&code=' + getQueryVariable('code') + '&grant_type=AUTHORIZATION_CODE&redirect_uri=http://localhost:4000'
RestClient.post 'http://gitlab.com/oauth/token', parameters //this is the code given by the docs
} else {
oauthcode = $.getJSON('http://gitlab.com/oauth/token?client_id=####&client_secret=####&code=' + getQueryVariable('code') + '&grant_type=AUTHORIZATION_CODE&redirect_uri=http://roconnor.gitlab.io/repoViewer');
}
console.log(oauthcode); //the error gets thrown here
Related
I'm trying to add access control to my Angular app and running into some puzzling troubles...
On the front-end, the authentication function is being called repeatedly until it is stopped for being too large of a call stack. Each time, the $http request within the function is triggering the errorCallback function. The expected behavior is for the auth function to fire once every time ui-router's state changes, changing $rootScope values that indicate the browser's level of authentication.
First, the factory responsible for making the GET request:
.factory ('authRequest', function ($http) {
return {
authStatus : function() {
$http.get('/auth', {'withCredentials' : true}).then(function successCallback(response) {
console.log("Successful authorization check.");
return response.status;
}, function errorCallback(response) {
if (response.status) {
console.log("Failed to authenticate.");
return response.status;
}
console.log("Failed to receive a response.");
return 'errNoResponse';
});
}
}
})
Then, the ng-controller for processing the factory's response:
//Navbar controller, set to fire upon each state change and verify authorization.
.controller('navCtrl', function ($rootScope, $state, authRequest) {
$rootScope.$on('$stateChangeStart', function (event, toState) {
console.log('authRequest value: ' + [authRequest]);
if (authRequest.authStatus === 202) {
console.log('Auth check succeeded, assigned user privileges.');
$rootScope.loggedIn = true;
$rootScope.loggedInAdmin = false;
if (toState = 'users' || 'login') {
event.preventDefault();
}
} else if (authRequest.authStatus === 222) {
console.log('Auth check succeeded, assigned admin privileges.');
$rootScope.loggedIn = true;
$rootScope.loggedInAdmin = true;
if (toState = 'login') {
event.preventDefault();
}
} else {
console.log('Auth check failed.');
$rootScope.loggedIn = false;
$rootScope.loggedInAdmin = false;
event.preventDefault();
$state.go('login');
}
});
})
Meanwhile, on the back-end, I'm not seeing evidence of the /auth Express route being reached with any of the requests. I have a console log set to go off when /auth receives a GET request, but I'm not seeing any activity in the console. Every other Express route is being accessed without issue. The expected behavior is to receive the request, decode the request's JWT cookie header, then send a response code back according to what sort of user privileges are listed. Here's the Express route for /auth:
// GET /auth. Fired every time the app state changes. Verifies JWT authenticity and sends a response based on the user's privileges. 202 is an auth'd user, 222 is an auth'd admin. 401 is no token or error.
app.get('/auth', function (req, res, next) {
console.log('Authorization request received.')
var decoded = jwt.verify(req.cookie, [JWTAuthSecret]);
if (decoded) {
if (decoded.admin === true) {
return res.status(222).send(res.status);
console.log(decoded.sub + ' is an admin, responded with code 222');
} else {
return res.status(202).send(res.status);
console.log(decoded.sub + ' is not an admin, responded with code 202');
}
} else {
return res.status(401).send(res.status);
console.log('Decode failed, responded with code 401');
};
});
With the current setup, the app is hanging indefinitely. As mentioned earlier, a ton of auth requests are being produced upon each state change. Each one logs an "authRequest value: [object Object]" then "Auth check failed." Eventually I get the following error:
angular.js:13550RangeError: Maximum call stack size exceeded
at angular.js:10225
at n.$broadcast (angular.js:17554)
at Object.transitionTo (angular-ui-router.js:3273)
at Object.go (angular-ui-router.js:3108)
at app.js:275
at n.$broadcast (angular.js:17552)
at Object.transitionTo (angular-ui-router.js:3273)
at Object.go (angular-ui-router.js:3108)
at app.js:275
at n.$broadcast (angular.js:17552)
So there seems to be a problem with the frequency of the calls on the front end, as well as a problem with actually getting the data sent to the /auth route.
This is my first time working with Angular factories, so my instinct is to assume my factory implementation is wonky... I haven't figured out how it might be fixed on my own, though.
Thanks for reading, hope I can get some advice on what to change to make this work.
I see a couple issues. This might not solve everything, but will hopefully help narrow things down.
One is that authRequest.authStatus is a function but you're never calling it. In your controller, you need to call the function. That's part of the reason nothing's pinging the backend.
authRequest.authStatus().then(function(status) {
if (status === 202) {
//do stuff
} else if (status === 222) {
//do other stuff
}
});
Now, in your factory, you're not returning anything to the function, so make sure you do that.
.factory ('authRequest', function ($http) {
return {
authStatus : function() {
return $http.get('url').then(callbacks);
}
}
})
The accepted answer provided me with solutions to the front-end troubles I was facing. The GET request to /auth was successfully being sent to Express as intended, but was still responding with an error code despite a valid authentication cookie.
Checking the backend logs, I was receiving an error saying JsonWebTokenError: jwt must be provided when trying to decode the JWT. Turns out req.cookie isn't the correct syntax to check the request's cookie - req.cookies is.
After changing that, my cookie output went from being undefined to [object Object]. The error I was getting changed as well to TypeError: jwtString.split is not a function.
After double checking the way cookies are referenced in the Express docs, I realized I wasn't calling the JWT cookie by name. The updated code is:
app.get('/auth', function (req, res, next) {
console.log('Authorization request received.')
console.log ('Cookie Data: ' + req.cookies.CookieName);
var decoded = jwt.verify(req.cookies.CookieName, [JWTAuthSecret]);
if (decoded) {
if (decoded.admin === true) {
return res.status(222).send(res.status);
console.log(decoded.sub + ' is an admin, responded with code 222');
} else {
return res.status(202).send(res.status);
console.log(decoded.sub + ' is not an admin, responded with code 202');
}
} else {
return res.status(401).send(res.status);
console.log('Decode failed, responded with code 401');
};
});
With this change and the front-end changes, the app's authentication and authorization are working as intended.
Is there a way to send signed version of secret key to AWS JS SDK, instead of sending secret Key adn access key in plain text. I am using Salesforce and would like to create a signature in Salesforce and send it to Javascript (or VF page) the signature which can then be used in AWS SDK callouts, instead of giving the secret key on the client side.
It's good that you see the obvious problem with embedding your credentials into the client.
Amazon Security Token Service (STS) is one solution to this issue.
Your application back-end systems send a request for temporary credentials to STS. These credentials allow the entity possessing them to perform only actions authorized by the token, which is a third attribute added to the usual (access key id, access key secret) tuple, and the authority is valid only until the token expires. Note that the token doesn't use your access key and secret, it actually comes with its own access key and secret, all of which are short-lived and "disposable."
In a sense, this is fairly well described by the phrase you used, "send signed version of secret key." It's a disposable delegation of authority, in a sense.
See also http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html.
I am finally able to make a call to STS from Salesforce using Apex Code, all thanks to Michael. Which then returns the Session Token, Temp Access Key and Temp Secret Key. Following is the gist of the code that I used to make a call to AWS STS.
public void testGetSessionToken() {
method = HttpMethod.XGET;
createSigningKey(AWS_SECRET_KEY_ID ) ;
service = 'sts';
resource = '/';
// Create all of the bits and pieces using all utility functions above
HttpRequest request = new HttpRequest();
request.setMethod(method.name().removeStart('X'));
setQueryParam('Version', '2011-06-15');
setQueryParam('Action', 'AssumeRole');
setQUeryParam('RoleArn', 'arn:aws:iam::669117241099:role/TestRole');
setQueryParam('RoleSessionName', 'Swai');
String formatedDate = requestTime.formatGmt('yyyyMMdd') + 'T' + '03' + requestTime.formatGmt('mmss') + 'Z';
setHeader('X-Amz-Date', formatedDate );
setHeader('host', endpoint.getHost());
finalEndpoint = 'https://sts.amazonaws.com' + resource + '?Version=2011-06-15&Action=AssumeRole&RoleArn=arn:aws:iam::559117241080:role/TestRole&RoleSessionName=Swai';
request.setEndpoint(finalEndpoint);
String[] headerKeys = new String[0];
String authHeader = createAuthorizationHeader(); //This is the method that gets the signature)
request.setHeader( 'host', 'sts.amazonaws.com');
request.setHeader( 'X-Amz-Date', formatedDate );
request.setHeader( 'Authorization', authHeader);
HttpResponse response = new Http().send(request);
// Parse XML to get SessionToken
XMLStreamReader xmlrdr = response.getXmlStreamReader();
while (xmlrdr.hasNext()) {
if (xmlrdr.getEventType() == XMLTag.START_ELEMENT ) {
if (xmlrdr.getLocalName() == 'SessionToken') {
while (xmlrdr.hasNext() && xmlrdr.getEventType() != XMLTag.END_ELEMENT){
if (xmlrdr.getEventType() == XMLTag.CHARACTERS) {
forPageSessionToken = xmlrdr.getText();
break;
} else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
}
} else if (xmlrdr.getLocalName() == 'AccessKeyId') {
while (xmlrdr.hasNext() && xmlrdr.getEventType() != XMLTag.END_ELEMENT){
if (xmlrdr.getEventType() == XMLTag.CHARACTERS) {
forPageTempAccessKeyId = xmlrdr.getText();
break;
} else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
}
} else if (xmlrdr.getLocalName() == 'SecretAccessKey') {
while (xmlrdr.hasNext() && xmlrdr.getEventType() != XMLTag.END_ELEMENT){
if (xmlrdr.getEventType() == XMLTag.CHARACTERS) {
forPageTempSecretAccessKey = xmlrdr.getText();
break;
} else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
}
}
else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
} else if (xmlrdr.hasNext()) xmlrdr.next();
else break;
}
}
Thanks a lot Michael for being so helpful and for your prompt responses. I am posting the solution that I have here so that it can benefit others, and hopefully not be stuck for this long.
I am having what I hope is an easy to solve problem with the Soundcloud API using JavaScript:
unauthorized, the following code works fine:
var group = 'https://soundcloud.com/groups/chilled';
SC.initialize({
client_id: 'MY_CLIENT_ID',
redirect_uri: 'http://localhost:49957/tn/callback.html'
});
// Resolve works fine and gives number ID of group
SC.resolve(group + '?client_id=' + client_id).then(function (g) {
console.log('Group 1: ' + g.id);
});
after I authorise a user:
SC.connect().then(function () {
return SC.get('/me');
}).then(function (me) {
authUser = me.id
});
// Resolve no longer works and i get 401 unauthorised
SC.resolve(group + '?client_id=' + client_id).then(function (g) {
console.log('Group 1: ' + g.id);
});
can anyone help me to understand what I am doing wrong - I can't seem to find an example to follow anywhere. Driving me potty!
Many thanks in advance,
James
For anyone else out there facing the same issues, I have answered my own question:
Firstly, I was not properly logged in due to an error on Soundcloud's sample code in their callback.html used to return to the site after oAuth client side login flow. In Soundcloud's sample callback.html, the code:
<body onload="window.opener.setTimeout(window.opener.SC.connectCallback, 1)">
must be altered to:
<body onload="window.setTimeout(window.opener.SC.connectCallback, 1)">
This allows the popup to close properly and completes the login flow if the application settings are correctly configured to the same domain (localhost or production, but not a mix of the two).
Further to this callback, i have added the following code:
var params = getSearchParameters();
window.opener.oAuthToken = params.code;
function getSearchParameters() {
var prmstr = window.location.search.substr(1);
return prmstr != null && prmstr != "" ? transformToAssocArray(prmstr) : {};
}
function transformToAssocArray(prmstr) {
var params = {};
var prmarr = prmstr.split("&");
for (var i = 0; i < prmarr.length; i++) {
var tmparr = prmarr[i].split("=");
params[tmparr[0]] = tmparr[1];
}
return params;
}
In my subsequent pages, I can get any data as a sub-resource of the '/me/...' endpoint now, but anything I used to be able to interrogate via public access is still in accessible. Since I can now iterate through the logged in users groups, I no longer have to resolve the name of a group via the public resource '/resolve/', so my issue is not solved, but avoided in my current development.
I was trying to implement oauth javascript client using Google app engine oauth end points.
I registered my myapp.appspot.com domain with my google account.
I was able to retrieve the verification.
But received a 400 error trying to fetch the access token.
Did anyone successfully receive an access token from Google app engine end points?
If so please help me.
Using the following code.
function getRequestToken() {
token = {};
oauthRequest("https://myapp.appspot.com/_ah/OAuthGetRequestToken" , { oauth_callback: "https://myapp.appspot.com/index"}, gotRequestToken);
}
function gotRequestToken() {
parseToken(req.responseText);
authorize();
}
function authorize() {
deleteTab = false;
chrome.tabs.create({
"url":"https://myapp.appspot.com/_ah/OAuthAuthorizeToken?oauth_token=" + token.oauth_token,
"selected":true
}, function(theTab) {
if(deleteTab){
chrome.tabs.remove(theTab.id);
} else {
deleteTab = theTab;
}
});
when i granted access to myapp.appspot.com, i get a oauth_verifier in the url, which i successfully captured and passed to the following request.
oauthRequest("https://myapp.appspot.com/_ah/OAuthGetAccessToken", { "oauth_verifier": data.verifier }, gotAccessToken);
function gotAccessToken() {
alert ('got access token');
parseToken(req.responseText);
storeToken();
}
But it is this final access token request that fails! giving 400 error.
And am using oauthsimple javascript library.
Trying to find solution for redirection to the login page after Ajax-call if a user is not authenticated longer.
I used a method described here
How to manage a redirect request after a jQuery Ajax call
private static void RedirectionToLogin(ActionExecutingContext filterContext)
{
string redirectOnSuccess = filterContext.HttpContext.Request.Url.AbsolutePath;
string redirectUrl = string.Format("?ReturnUrl={0}", redirectOnSuccess);
string loginUrl = FormsAuthentication.LoginUrl + redirectUrl;
filterContext.HttpContext.Response.AddHeader("REQUIRES_AUTH", "1");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.Redirect(loginUrl, true);
}
JavaScript is
$(window).ajaxComplete(function (event, request, settings) {
alert(request.getResponseHeader('REQUIRES_AUTH'));
alert(request.status);
if (request.getResponseHeader('REQUIRES_AUTH') === 1) {
window.location = App.basePath + "Login/LogOn";
{
});
If user was logged out request.getResponseHeader('REQUIRES_AUTH')) is always NULL
Why request.getResponseHeader('REQUIRES_AUTH')) is NULL???
I think the reason you are getting a null value for "REQUIRES_AUTH" is that your log in page
~/Login/Logon
does not require authentication. Which is correct, otherwise you would not be able to log in! Also your javascript has a curly bracket the wrong way around. (typo) should be:
$(window).ajaxComplete(function (event, request, settings) {
alert(request.getResponseHeader('REQUIRES_AUTH'));
alert(request.status);
if (request.getResponseHeader('REQUIRES_AUTH') === 1) {
window.location = App.basePath + "Login/LogOn";
}
});
I am also looking for a nice solution for this problem, but am still a bit stuck. Have you managed to solve this problem? if so, how did you manage it?