I'm in the process of designing an API in PHP that will use OAuth2.0. My end goal is to build a front-end application in javascript (using AngularJS) that accesses this API directly. I know that traditionally there's no way to secure transactions in javascript and so directly accessing an API isn't feasible. The front-end would need to communicate with server code that in turn communicated with the API directly. However, in researching OAuth2 it looks as if the User-Agent Flow is designed to help in this situation.
What I need help with is implementing the OAuth2 User-Agent Flow in javascript (particularly AngularJS if possible as that's what I'm using for my front-end). I haven't been able to find any examples or tutorials that do this. I really have no idea where to start and don't want to read through the entire OAuth2 spec without at least seeing an example of what I'll be looking at doing. So any examples, tutorials, links, etc would be greatly appreciated.
The Implicit Grant flow (the one you're referring to as User-Agent Flow) is exactly the way to go:
The implicit grant is a simplified authorization code flow optimized for clients implemented in a browser using a scripting language such as JavaScript.
To understand the flow, the documentation from Google for client-side applications is a really good place to start. Note that they recommend you to take an additional token validation step to avoid confused deputy problems.
Here is a short example implementation of the flow using the Soundcloud API and jQuery, taken from this answer:
<script type="text/javascript" charset="utf-8">
$(function () {
var extractToken = function(hash) {
var match = hash.match(/access_token=([\w-]+)/);
return !!match && match[1];
};
var CLIENT_ID = YOUR_CLIENT_ID;
var AUTHORIZATION_ENDPOINT = "https://soundcloud.com/connect";
var RESOURCE_ENDPOINT = "https://api.soundcloud.com/me";
var token = extractToken(document.location.hash);
if (token) {
$('div.authenticated').show();
$('span.token').text(token);
$.ajax({
url: RESOURCE_ENDPOINT
, beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', "OAuth " + token);
xhr.setRequestHeader('Accept', "application/json");
}
, success: function (response) {
var container = $('span.user');
if (response) {
container.text(response.username);
} else {
container.text("An error occurred.");
}
}
});
} else {
$('div.authenticate').show();
var authUrl = AUTHORIZATION_ENDPOINT +
"?response_type=token" +
"&client_id=" + clientId +
"&redirect_uri=" + window.location;
$("a.connect").attr("href", authUrl);
}
});
</script>
<style>
.hidden {
display: none;
}
</style>
<div class="authenticate hidden">
<a class="connect" href="">Connect</a>
</div>
<div class="authenticated hidden">
<p>
You are using token
<span class="token">[no token]</span>.
</p>
<p>
Your SoundCloud username is
<span class="user">[no username]</span>.
</p>
</div>
For sending XMLHttpRequests (what the ajax() function does in jQuery) using AngularJS, refer to their documentation of the $http service.
If you want to preserve state, when sending the user to the authorization endpoint, check out the state parameter.
There's an example of Authorization Code Grant approach to get a token from OAuth server. I used jQuery ($) to make some operations.
First, redirect user to authorization page.
var authServerUri = "http://your-aouth2-server.com/authorize",
authParams = {
response_type: "code",
client_id: this.model.get("clientId"),
redirect_uri: this.model.get("redirectUri"),
scope: this.model.get("scope"),
state: this.model.get("state")
};
// Redirect to Authorization page.
var replacementUri = authServerUri + "?" + $.param(authParams);
window.location.replace(replacementUri);
When one gave authorization pass to get token:
var searchQueryString = window.location.search;
if ( searchQueryString.charAt(0) === "?") {
searchQueryString = searchQueryString.substring(1);
}
var searchParameters = $.deparam.fragment(searchQueryString);
if ( "code" in searchParameters) {
// TODO: construct a call like in previous step using $.ajax() to get token.
}
You could implement the Resource Owner Password Credentials Grant in the same manner using jQuery or pure XMLHttpRequest and don't make any redirects - because on each redirect you'll loose state of your application.
For me, I used HTML5 local storage to persist state of my application for data which were not likely to pose a security threat.
Related
I am using AngularJS and trying to work with Google's reCAPTCHA,
I am using the "Explicitly render the reCAPTCHA widget" method for displaying the reCAPTCHA on my web page,
HTML code -
<script type="text/javascript">
var onloadCallback = function()
{
grecaptcha.render('loginCapcha', {
'sitekey' : 'someSiteKey',
'callback' : verifyCallback,
'theme':'dark'
});
};
var auth='';
var verifyCallback = function(response)
{
//storing the Google response in a Global js variable auth, to be used in the controller
auth = response;
var scope = angular.element(document.getElementById('loginCapcha')).scope();
scope.auth();
};
</script>
<div id="loginCapcha"></div>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
So far, I am able to achieve the needed functionality of whether the user is a Human or a Bot,
As per my code above, I have a Callback function called 'verifyCallback' in my code,
which is storing the response created by Google, in a global variable called 'auth'.
Now, the final part of reCAPCHA is calling the Google API, with "https://www.google.com/recaptcha/api/siteverify" as the URL and using a POST method,And passing it the Secret Key and the Response created by Google, which I've done in the code below.
My Controller -
_myApp.controller('loginController',['$rootScope','$scope','$http',
function($rootScope,$scope,$http){
var verified = '';
$scope.auth = function()
{
//Secret key provided by Google
secret = "someSecretKey";
/*calling the Google API, passing it the Secretkey and Response,
to the specified URL, using POST method*/
var verificationReq = {
method: 'POST',
url: 'https://www.google.com/recaptcha/api/siteverify',
headers: {
'Access-Control-Allow-Origin':'*'
},
params:{
secret: secret,
response: auth
}
}
$http(verificationReq).then(function(response)
{
if(response.data.success==true)
{
console.log("Not a Bot");
verified = true;
}
else
{
console.log("Bot or some problem");
}
}, function() {
// do on response failure
});
}
So, the Problem I am actually facing is that I am unable to hit the Google's URL, Following is the screenshot of the request I am sending and the error.
Request made -
Error Response -
As far as I understand it is related to CORS and Preflight request.So what am I doing wrong? How do I fix this problem?
As stated in google's docs https://developers.google.com/recaptcha/docs/verify
This page explains how to verify a user's response to a reCAPTCHA challenge from your application's backend.
Verification is initiated from the server, not the client.
This is an extra security step for the server to ensure requests coming from clients are legitimate. Otherwise a client could fake a response and the server would be blindly trusting that the client is a verified human.
If you get a cors error when trying to sign in with recaptcha, it could be that your backend server deployment is down.
I'm implementing a website in Angular.js, which is hitting an ASP.NET WebAPI backend.
Angular.js has some in-built features to help with anti-csrf protection. On each http request, it will look for a cookie called "XSRF-TOKEN" and submit it as a header called "X-XSRF-TOKEN" .
This relies on the webserver being able to set the XSRF-TOKEN cookie after authenticating the user, and then checking the X-XSRF-TOKEN header for incoming requests.
The Angular documentation states:
To take advantage of this, your server needs to set a token in a JavaScript readable session cookie called XSRF-TOKEN on first HTTP GET request. On subsequent non-GET requests the server can verify that the cookie matches X-XSRF-TOKEN HTTP header, and therefore be sure that only JavaScript running on your domain could have read the token. The token must be unique for each user and must be verifiable by the server (to prevent the JavaScript making up its own tokens). We recommend that the token is a digest of your site's authentication cookie with salt for added security.
I couldn't find any good examples of this for ASP.NET WebAPI, so I've rolled my own with help from various sources. My question is - can anyone see anything wrong with the code?
First I defined a simple helper class:
public class CsrfTokenHelper
{
const string ConstantSalt = "<ARandomString>";
public string GenerateCsrfTokenFromAuthToken(string authToken)
{
return GenerateCookieFriendlyHash(authToken);
}
public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken)
{
return csrfToken == GenerateCookieFriendlyHash(authToken);
}
private static string GenerateCookieFriendlyHash(string authToken)
{
using (var sha = SHA256.Create())
{
var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt));
var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash);
return cookieFriendlyHash;
}
}
}
Then I have the following method in my authorisation controller, and I call it after I call FormsAuthentication.SetAuthCookie():
// http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
// http://docs.angularjs.org/api/ng.$http
private void SetCsrfCookie()
{
var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH");
Debug.Assert(authCookie != null, "authCookie != null");
var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value);
var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false};
HttpContext.Current.Response.Cookies.Add(csrfCookie);
}
Then I have a custom attribute which I can add to controllers to make them check the csrf header:
public class CheckCsrfHeaderAttribute : AuthorizeAttribute
{
// http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc
protected override bool IsAuthorized(HttpActionContext context)
{
// get auth token from cookie
var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"];
if (authCookie == null) return false;
var authToken = authCookie.Value;
// get csrf token from header
var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
if (String.IsNullOrEmpty(csrfToken)) return false;
// Verify that csrf token was generated from auth token
// Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header.
// This proves that our site made the request.
return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken);
}
}
Lastly, I clear the Csrf token when the user logs out:
HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");
Can anyone spot any obvious (or not-so-obvious) problems with that approach?
Your code seems to be fine. The only thing is, you don't need most of the code you have as web.api runs "on top" of asp.net mvc, and latter has built in support for anti-forgery tokens.
In comments dbrunning and ccorrin express concerns that you only able to use build in AntiForgery tokens only when you are using MVC html helpers. It is not true. Helpers can just expose session based pair of tokens that you can validate against each other. See below for details.
UPDATE:
There is two methods you can use from AntiForgery:
AntiForgery.GetTokens uses two out parameters to return cookie token and form token
AntiForgery.Validate(cookieToken, formToken) validates if pair of tokens is valid
You totally can repurpose those two methods and use formToken as headerToken and cookieToken as actual cookieToken. Then just call validate on both within attribute.
Another solution is to use JWT (check eg MembershipReboot implementation)
This link shows how to use built in anti-forgery tokens with ajax:
<script>
#functions{
public string TokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
$.ajax("api/values", {
type: "post",
contentType: "application/json",
data: { }, // JSON data goes here
dataType: "json",
headers: {
'RequestVerificationToken': '#TokenHeaderValue()'
}
});
</script>
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
Also take a look at this question AngularJS can't find XSRF-TOKEN cookie
This solution isn't secure since CSRF attacks are still possible as long as the Auth cookie is valid. Both the auth and the xsrf cookie will be sent to the server when an attacker makes you perform a request via another site, and therefore you are still vulnerable until the user does a "hard" logout.
Each request or session should have its own unique token to truly prevent CRSF attacks. But probably the best solution is to not use cookie based authentication but token based authentication such as OAuth. This prevents other websites from using your cookies to perform unwanted requests, since the tokens are used in http headers instead of cookies. And http headers are not automatically send.
Token Based Authentication using ASP.NET Web API 2, Owin, and Identity
AngularJS Token Authentication using ASP.NET Web API 2, Owin, and Identity
These excellent blog posts contain information of how to implement OAuth for WebAPI. The blog posts also contains great information of how to integrate it with AngularJS.
Another solution might be to disable CORS and only accept incoming requests from whitelisted domains. However this won't work for non-website applications, such as mobile and/or desktop clients. Next to that once your website is vulnerable to a XSS attack the attacker will still be able to forge requests on your behalve.
I think your code is flawed. The whole idea around prevent CSRF is to prevent a unique token on each REQUEST, not each session. If the anti-forgery token is a session persisted value, the ability to perform CSRF still remains. You need to provide a unique token on each request...
Haven't had any problems pointed out with the code, so I consider the question answered.
I am attempting to make an API OAUTH2 call to Shopify to authenticate. When I go to my application, the first screen comes up. I press Install, and then it redirects me back to where I started. The problem is that when I get redirected, I cannnot seem to catch it in my jQuery ajax function and therefore I am unable to get the temporary token I need to create my permanent token from OAUTH. The 2nd image shows what is happening after I press install. My code so far is posed below. None of the console.log() functions are being called after I run the ajax call.
My application URL for my app is set to
http://localhost
and my application is running from
http://localhost/shippingcalculator.
I tested the calls in an external REST client program, and I managed to successfully get my access token, so it is not a problem with my credentials.
<!DOCTYPE html>
<script type="text/javascript">
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
console.log(window.location.search);
var results = regex.exec(window.location.search);
if (results == null) return "";
else return decodeURIComponent(results[1].replace(/\+/g, " "));
}
function getTemporaryToken(token) {
jso_configure({
"shopify": {
client_id: "67d7af798dd0a42e731af51ffa", //This is your API Key for your App
//redirect_uri: "", OPTIONAL - The URL that the merchant will be sent to once authentication is complete. Must be the same host as the Return URL specified in the application settings
authorization: "https://emard-ratke3131.myshopify.com/admin/oauth/authorize",
//In your case the authorization would be https://SHOP_NAME.myshopify.com/admin/oauth/authorize
scope: ["read_products"], //Set the scope to whatever you want to access from the API. Full list here: http://api.shopify.com/authentication.html
}
});
$.oajax({
url: "https://emard-ratke3131.myshopify.com/admin/oauth/authorize",
jso_provider: "shopify",
jso_scopes: ["read_products"],
jso_allowia: true,
success: function(data) {
//Use data and exchange temporary token for permanent one
if (jqXHR.status === 200) {
console.log("200");
}
if (jqXHR.status === 302) {
console.log("300");
}
console.log("Response (shopify):");
console.log(data);
},
error: function(e) {
console.log("Fail GET Request");
console.log(e);
},
complete: function(xmlHttp) {
// xmlHttp is a XMLHttpRquest object
console.log(xmlHttp.status);
}
});
console.log("Code: " + getParameterByName("code"));
}
}
$(document).ready(function() {
getTemporaryToken();
});
</script>
</head>
<body>
Hello World!
</body>
</html>
It's a pretty bad idea to do all of this client side, your API key will be in the clear for anyone to see and use. On top of that where will you store the token? There are also cross site scripting restrictions built into all browsers that will prevent JavaScript from making calls to sites other than the one the js is loaded from.
You should really do the authentication and make all your API calls from a server.
I'm trying to authorize my application to integrate with Google Drive. Google documentation provides details for server based authorization and code samples for various server technologies.
There's also a JavaScript Google API library, that has support for authorization. Down in the samples section of the wiki there is a code snippet for creating a config and calling the authorize function. I've altered the scope to be that one I believe is required for drive:
var config = {
'client_id': 'my_client_ID',
'scope': 'https://www.googleapis.com/auth/drive.file'
};
gapi.auth.authorize(config, function() {
console.log(gapi.auth);
});
The callback function is never called (yes, the Google API library is loaded corrected) Looking the Java Retrieve and Use OAuth 2.0 Credentials example, the client secret seems to be a parameter, should this go into the config?
Has anyone tried this in JS, for Drive or other Google APIs? Does anyone know the best route for debugging such a problem, i.e. do I need to just step through the library and stop whinging?
Please don't suggest doing the authorization on the server side, our application is entirely client side, I don't want any state on the server (and I understand the token refresh issues this will cause). I am familiar with the API configuration in the Google console and I believe that and the drive SDK setting are correct.
It is possible to use the Google APIs Javascript client library with Drive but you have to be aware that there are some pain points.
There are 2 main issues currently, both of which have workarrounds:
Authorization
First if you have a look closely at how Google Drive auth works you will realize that, after a user has installed your Drive application and tries to open a file or create a new file with your application, Drive initiates the OAuth 2.0 authorization flow automatically and the auth parameters are set to response_type=code and access_type=offline.
This basically means that right now Drive apps are forced to use the OAuth 2 server-side flow which is not going to be of any use to the Javascript client library (which only uses the client-side flow).
The issue is that: Drive initiates a server-side OAuth 2.0 flow, then the Javascript client library initiates a client-side OAuth 2.0 flow.
This can still work, all you have to do it is use server-side code to process the authorization code returned after the Drive server-side flow (you need to exchange it for an access token and a refresh token).
That way, only on the first flow will the user be prompted for authorization. After the first time you exchange the authorization code, the auth page will be bypassed automatically.
Server side samples to do this is available in our documentation.
If you don't process/exchange the auth code on the server-side flow, the user will be prompted for auth every single time he tries to use your app from Drive.
Handling file content
The second issue is that uploading and accessing the actual Drive file content is not made easy by our Javascript client library. You can still do it but you will have to use custom Javascript code.
Reading the file content
When a file metadata/a file object is retrieved, it contains a downloadUrl attribute which points to the actual file content. It is now possible to download the file using a CORS request and the simplest way to auth is to use the OAuth 2 access token in a URL param. So just append &access_token=... to the downloadUrl and fetch the file using XHR or by forwarding the user to the URL.
Uploading file content
UPDATE UPDATE: The upload endpoints do now support CORS.
~~UPDATE: The upload endpoints, unlike the rest of the Drive API do not support CORS so you'll have to use the trick below for now:~~
Uploading a file is tricky because it's not built-in the Javascript client lib and you can't entirely do it with HTTP as described in this response because we don't allow cross-domain requests on these API endpoints. So you do have to take advantage of the iframe proxy used by our Javascript client library and use it to send a constructed multipart request to the Drive SDK. Thanks to #Alain, we have an sample of how to do that below:
/**
* Insert new file.
*
* #param {File} fileData File object to read data from.
* #param {Function} callback Callback function to call when the request is complete.
*/
function insertFileData(fileData, callback) {
const boundary = '-------314159265358979323846';
const delimiter = "\r\n--" + boundary + "\r\n";
const close_delim = "\r\n--" + boundary + "--";
var reader = new FileReader();
reader.readAsBinaryString(fileData);
reader.onload = function(e) {
var contentType = fileData.type || 'application/octet-stream';
var metadata = {
'title': fileData.fileName,
'mimeType': contentType
};
var base64Data = btoa(reader.result);
var multipartRequestBody =
delimiter +
'Content-Type: application/json\r\n\r\n' +
JSON.stringify(metadata) +
delimiter +
'Content-Type: ' + contentType + '\r\n' +
'Content-Transfer-Encoding: base64\r\n' +
'\r\n' +
base64Data +
close_delim;
var request = gapi.client.request({
'path': '/upload/drive/v2/files',
'method': 'POST',
'params': {'uploadType': 'multipart'},
'headers': {
'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
},
'body': multipartRequestBody});
if (!callback) {
callback = function(file) {
console.log(file)
};
}
request.execute(callback);
}
}
To improve all this, in the future we might:
Let developers choose which OAuth 2.0 flow they want to use (server-side or client-side) or let the developer handle the OAuth flow entirely.
Allow CORS on the /upload/... endpoints
Allow CORS on the exportLinks for native gDocs
We should make it easier to upload files using our Javascript client library.
No promises at this point though :)
I did it. Heres my code:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<style>
p {
font-family: Tahoma;
}
</style>
</head>
<body>
<!--Add a button for the user to click to initiate auth sequence -->
<button id="authorize-button" style="visibility: hidden">Authorize</button>
<script type="text/javascript">
var clientId = '######';
var apiKey = 'aaaaaaaaaaaaaaaaaaa';
// To enter one or more authentication scopes, refer to the documentation for the API.
var scopes = 'https://www.googleapis.com/auth/drive';
// Use a button to handle authentication the first time.
function handleClientLoad() {
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeButton = document.getElementById('authorize-button');
if (authResult && !authResult.error) {
authorizeButton.style.visibility = 'hidden';
makeApiCall();
} else {
authorizeButton.style.visibility = '';
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick(event) {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
return false;
}
// Load the API and make an API call. Display the results on the screen.
function makeApiCall() {
gapi.client.load('drive', 'v2', function() {
var request = gapi.client.drive.files.list ( {'maxResults': 5 } );
request.execute(function(resp) {
for (i=0; i<resp.items.length; i++) {
var titulo = resp.items[i].title;
var fechaUpd = resp.items[i].modifiedDate;
var userUpd = resp.items[i].lastModifyingUserName;
var fileInfo = document.createElement('li');
fileInfo.appendChild(document.createTextNode('TITLE: ' + titulo + ' - LAST MODIF: ' + fechaUpd + ' - BY: ' + userUpd ));
document.getElementById('content').appendChild(fileInfo);
}
});
});
}
</script>
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
<p><b>These are 5 files from your GDrive :)</b></p>
<div id="content"></div>
</body>
</html>
You only have to change:
var clientId = '######';
var apiKey = 'aaaaaaaaaaaaaaaaaaa';
to your clientID and ApiKey from your Google API Console :)
Of course you have to create your project on Google API Console, activate the Drive API and activate Google Accounts auth in OAuth 2.0 (really eeeeasy!)
PS: it wont work locally on your PC, it will work on some hosting, and yoy must provide the url from it on the project console :)
In the authentication flow documentation here it mentions the CODE which is returned upon oAuth authentication.
Is this required for the Javascript SDK or is this handled automatically in the background in this code?
By "is this required?" I mean, do I have to handle this code to verify the authenticity of the request, or does the JavaScript SDK use the code automatically to gain the access_token.
The documentation explains the client side flow, and how to get the access token using the 'code' so until now. I've been assuming that the SDK manages this automatically in the background, because it produces an access code as response.authResponse.accessToken.
FB.login(function(response) {
if (response.authResponse) {
// User is logged in to Facebook and accepted permissions
// Assign the variables required
var access_token = response.authResponse.accessToken;
var fb_uid = response.authResponse.userID;
alert(dump(response.authResponse));
// Construct data string to pass to create temporary session using PHP
var fbDataString = "uid=" + fb_uid + "&access_token=" + access_token;
// Call doLogin.php to log the user in
$.ajax({
type: "POST",
url: "ajax/doLogin.php",
data: fbDataString,
dataType: "json",
success: function(data) {
// Get JSON response
if (data.result == "failure")
{
alert(data.error_message);
window.location.reload();
return false;
}
else if (data.result == "success")
{
window.location.reload();
return true;
}
},
error: function() {
return false;
}
});
} else {
// user is not logged in and did not accept any permissions
return false;
}
}, {scope:'publish_stream,email'});
I would like to know, because I want to ensure that my code is secure.
From the documentation
With this code in hand, you can proceed to the next step, app authentication, to gain the access token you need to make API calls.
In order to authenticate your app, you must pass the authorization code and your app secret to the Graph API token endpoint at https://graph.facebook.com/oauth/access_token. The app secret is available from the Developer App and should not be shared with anyone or embedded in any code that you will distribute (you should use the client-side flow for these scenarios).
If you plan on using the FB.api function to make calls to their Graph API, then you need the code to get the access token. But if you only need to authenticate the user, then what you have will do that just fine.