I'm trying to integrate a functionality in a webapp, where a user clicks a button to tweet how awesome the webapp is. (I know, I know, stupid, but hey, the boss wants it)
I set up the route below using Thujohn's Twitter for Laravel 4 - https://github.com/thujohn/twitter-l4
Route::get('/twitter/callback', function() {
// You should set this route on your Twitter Application settings as the callback
// https://apps.twitter.com/app/YOUR-APP-ID/settings
if(Session::has('oauth_request_token')) {
$request_token = array(
'token' => Session::get('oauth_request_token'),
'secret' => Session::get('oauth_request_token_secret'),
);
Twitter::set_new_config($request_token);
$oauth_verifier = FALSE;
if(Input::has('oauth_verifier')) {
$oauth_verifier = Input::get('oauth_verifier');
}
// getAccessToken() will reset the token for you
$token = Twitter::getAccessToken( $oauth_verifier );
if( !isset( $token['oauth_token_secret'] ) ) {
return Redirect::to('/')->with('flash_error', 'We could not log you in on Twitter.');
}
$credentials = Twitter::query('account/verify_credentials');
if( is_object( $credentials ) && !isset( $credentials->error ) ) {
// $credentials contains the Twitter user object with all the info about the user.
// Add here your own user logic, store profiles, create new users on your tables...you name it!
// Typically you'll want to store at least, user id, name and access tokens
// if you want to be able to call the API on behalf of your users.
// This is also the moment to log in your users if you're using Laravel's Auth class
// Auth::login($user) should do the trick.
var_dump($credentials);
//return Redirect::to('/')->with('flash_notice', "Congrats! You've successfully signed in!");
}
return Redirect::to('/')->with('flash_error', 'Crab! Something went wrong while signing you up!');
}
});
However, I don't really know what I should do in this part:
// $credentials contains the Twitter user object with all the info about the user.
// Add here your own user logic, store profiles, create new users on your tables...you name it!
// Typically you'll want to store at least, user id, name and access tokens
// if you want to be able to call the API on behalf of your users.
// This is also the moment to log in your users if you're using Laravel's Auth class
// Auth::login($user) should do the trick.
What do I need to do to make sure a user doesn't need to authenticate himself every time?
If you inspect the $credentials object, you should see oauth access tokens. Save these along with your user data (username, email, name, etc) in your database, and you can use them next time you make a call to the twitter API.
Related
I would like a user to login with tikok on the web and get his basic information like:
avarat_url
union_id (uniq user identifier provided by tiktok)
display_name
The Tiktok Login Kit for Web Documentation seems to be missing a full example on how to implement the full sequence of calls. Also some things are not explained at all (like the callback URL). Can someone share their full solution with code example on how to integrate tiktok login onto a webpage.
Heres a full example of the tiktok login for web implementation:
setup a tiktok developer account https://developers.tiktok.com/
create a new app, this will generate a CLIENT_KEY and CLIENT_SECRET.
create 2 backend endpoints that you control, for example:
https://example.com/auth : builds a tiktok URL and redirects the user to that endpoint (where the user will be prompted to login).
https://example.com/authCallback : once the user has finished the login with tiktok flow, tiktok sends an authorizationResponse to this endpoint. The authorizationResponse contains info that you need to fetch the users data.
in section "Platform Info": insert the callback URL and redirect domain. The callback URL being the second of the 2 server endpoints listed above. Tiktok will send the authorizationResponse to that URL once the user successfully loggs in and grants or declines the required permissions. In redirect domain simply add the domain without the exact path.
fill out all info for your app and wait for approval, this can take up to 1-3 days.
once approved, you are ready to implement the full flow, which consists of multiple steps/requests.
(A) send the user from your frontend to your first backend endpoint https://example.com/auth. From there, the user will be redirected to the tiktok auth page.
(B) once the user finished the authorization, tiktok sends a authorizationResponse to your callback URL (https://example.com/authCallback), which contains a variable code. With the code you can request the access_token and open_id of the user.
(C) use the access_token and open_id to request basic user info.
(A) Send User to Tiktok Authentication Page
In your frontend, redirect the user to https://example.com/auth. Then run the following nodejs backend code on your /auth route. For this example we use an express app (req = request object, res = response object):
// IMPORTANT, it is your responsibility to store a csrf token
// in your database, to be able to prevent xss attacks, read more
// here (section 2.1) => https://developers.tiktok.com/doc/login-kit-web
const createCsrfState = () => Math.random().toString(36).substring(7);
const csrfState = createCsrfState();
res.cookie('csrfState', csrfState, { maxAge: 60000 });
let url = 'https://open-api.tiktok.com/platform/oauth/connect/';
url += `?client_key=${CLIENT_KEY}`;
url += '&scope=user.info.basic';
url += '&response_type=code';
url += `&redirect_uri=${encodeURIComponent('https://example.com/authCallback')}`;
url += '&state=' + csrfState;
// redirect the user to the generated URL
// user will be prompted to login with tiktok
// and authorize needed permissions
res.redirect(url);
This code redirects the user to a tiktok url, where the user is prompted to sign in with tiktok and grant access.
(B) Handle authorizationResponse, use code to get access_token and open_id
Once the user finished the login process, tiktok sends an authorizationResponse to your second backend server endpoint https://example.com/authCallback. In that callback you recieve variables state and code.
// express example with
// `req` = request object
// `res` = response object
// check if the csrf token is valid
// its the developers responsibility
// to setup a validation logic.
if (!validateCsrfToken(req.query.state)) {
throw new Error("invalid csrf token");
}
async function getAccessTokenAndOpenId(code, TIKTOK_CLIENT_KEY, TIKTOK_CLIENT_SECRET) {
let urlAccessToken = 'https://open-api.tiktok.com/oauth/access_token/';
urlAccessToken += '?client_key=' + TIKTOK_CLIENT_KEY;
urlAccessToken += '&client_secret=' + TIKTOK_CLIENT_SECRET;
urlAccessToken += '&code=' + code;
urlAccessToken += '&grant_type=authorization_code';
const resp = await axios.post(urlAccessToken);
return {
accessToken: resp.data.data.access_token,
openId: resp.data.data.open_id,
};
}
const code = req.query.code;
const { openId, accessToken } = await getAccessTokenAndOpenId(code, TIKTOK_CLIENT_KEY, TIKTOK_CLIENT_SECRET);
(C) Get basic user info
async function getBasicInfo(accessToken, openId) {
let urlBasicInfo = `https://open-api.tiktok.com/user/info/`;
const data = {
access_token: accessToken,
open_id: openId,
fields: [
"open_id",
"union_id",
"avatar_url",
"avatar_url_100",
"avatar_url_200",
"avatar_large_url",
"display_name",
],
};
const resp = await axios.post(urlBasicInfo, data);
return resp.data.data.user;
}
const userBasicInfo = await getBasicInfo(accessToken, openId);
// 🥳 done!
At my current project, we are using Auth0 as our Identity Provider. The current architecture is just a ReactJS app supported by a couple of APIs. Each API requires different combinations of Authorization Scopes, but basically they will require Customer Role, Provider Role or any authenticated user.
We were using Username-Password-Authentication so far and now we are integrating Social logins (such as Facebook, Google and Apple).
In order to achieve so, we are using Authorization Code flow, so the BE constructs the Authorize URL (including Callback URL, scopes, etc) that the FE then uses. After the user has authenticated against the Social Provider, the Callback URL is called, we exchange the code for an access_token that is ultimately returned to the FE. So far so good.
https://{domain}.auth0.com/authorize?
response_type=code&
client_id={clientId}&
audience={audience}&
connection=facebook&
state={ramdom_value}&
redirect_uri={callbackUrl}&
scope=offline_access openid scope:customer
And here is where some issues arise.
Firstly, after exchanging the Authentication Code for an access_token, the token does not include the scopes in it, so the user cannot access the APIs. I had to create a custom rule that adds the Customer role, like this:
function (user, context, callback) {
var count = context.stats && context.stats.loginsCount ? context.stats.loginsCount : 0;
if (count > 1 || (context.connection !== 'facebook' && context.connection !== 'google-oauth2' && context.connection !== 'apple')) {
return callback(null, user, context);
}
var ManagementClient = require('auth0#2.17.0').ManagementClient;
var management = new ManagementClient({
token: auth0.accessToken,
domain: auth0.domain
});
management.assignRolestoUser(
{ id : user.user_id},
{ "roles" :["rol_Msm9ykmstuK09r9s"]},
function (err) {
if (err) {
callback(err);
} else {
callback(null, user, context);
}
}
);
}
I don't really understand why I need to create the rule in order to get a valid access_token.
Secondly, there are two possible roles for users, Customers and Providers. For now, we are only allowing customers to use Social Logins, but eventually we will need to support also Providers. There is no way for us to detect what kind of user is actually logging in within that rule.
So my question here would be how to solve it.
My final goal is to allow users (both Customers and Providers) to log in using Social Connections and have each of them with the roles they really require. Of course, I need to get a valid access_token so that users can then interact with our APIs.
Any thoughts or comments? What am I missing?
I came up with an elegant solution after all.
The approach I took was:
Create a Custom Rule that assigns both roles (Customer and Provider) only and only if:
1.1. This is the first login for this user
1.2. The connection type is either facebook or google-oauth2 or apple
When creating the URL for login, include only the scopes required based on the user role required. In addition, the callback url will include the user role in it, e.g. https://server/{platform}/callback/{role}
In the callback endpoint, remove the roles that are not required using the Auth0 Management API /api/v2/users/{id}/roles
This solution is a bit tricky, but works with relatively small coding and effort.
I am using the cloudspace angularjs-devise library on the client. When I try to login/register I get a 200 ok response with the plain user object visible in the chrome js console. Refreshing the page seems to lose this information even though I assumed that the service would store this at some point since it also has logout and currentUser methods.
https://github.com/cloudspace/angular_devise
My questions are:
1) Is this service actually storing the user and if so how (i.e. with cookies or localstorage or in memory)?
2) If the service does not store the user how can I store this information in a custom cookie/localstorage and more importantly set the user into the service so that the services "isauthenticated" and "currentuser" methods can be used?
Partial Library Readme Instructions
Just register Devise as a dependency for your module. Then, the Auth service will be available for use.
angular.module('myModule', ['Devise']).
config(function(AuthProvider) {
// Configure Auth service with AuthProvider
}).
controller('myCtrl', function(Auth) {
// Use your configured Auth service.
});
Auth.login(creds): Use Auth.login() to authenticate with the server. Keep in mind, credentials are sent in plaintext; use a SSL connection to secure them. creds is an object which should contain any credentials needed to authenticate with the server. Auth.login() will return a promise that will resolve to the logged-in user. See AuthProvider.parse() for parsing the user into a usable object.
angular.module('myModule', ['Devise']).
controller('myCtrl', function(Auth) {
var credentials = {
email: 'user#domain.com',
password: 'password1'
};
Auth.login(credentials).then(function(user) {
console.log(user); // => {id: 1, ect: '...'}
}, function(error) {
// Authentication failed...
});
});
My partial code:
main.js
var myApp = angular.module('mail_app', ['ngRoute', 'ngResource', 'Devise']);
myApp.config(function($routeProvider, $locationProvider, $httpProvider, AuthProvider) {
console.log("in router")
$locationProvider.html5Mode(true);
$httpProvider.defaults.headers.common['X-CSRF-Token'] =
$('meta[name=csrf-token]').attr('content');
$httpProvider.defaults.headers.common['ClientType'] = 'browser';
// Customise login
AuthProvider.loginMethod('POST');
AuthProvider.loginPath('/api/v1/users/login.json');
// Customise register
AuthProvider.registerMethod('POST');
AuthProvider.registerPath('/api/v1/users.json');
});
SessionsController.js
myApp.controller('SessionsController', ['$scope', 'Auth', '$http', function($scope, Auth, $http) {
console.log("in session controller")
console.log(Auth.isAuthenticated());
$scope.loginUser = function() {
console.log("in login")
var credentials = {
email: $scope.email,
password: $scope.password
};
Auth.login(credentials).then(function(user) {
$scope.authError = 'Success!';
console.log(user); // => {id: 1, ect: '...'}
Auth.currentUser = user;
}, function(error) {
$scope.authError = 'Authentication failed...';
});
};
$scope.registerUser = function(){
console.log("in register function")
var ncredentials = {
email: $scope.newEmail,
password: $scope.newPassword,
password_confirmation: $scope.newPasswordConfirmation
};
Auth.register(ncredentials).then(function(registeredUser) {
console.log(registeredUser); // => {id: 1, ect: '...'};
}, function(error) {
$scope.authError = 'Registration failed...';
});
};
$scope.getCurrentUser = function(){
Auth.currentUser().then(function(user) {
// User was logged in, or Devise returned
// previously authenticated session.
console.log(user); // => {id: 1, ect: '...'}
$scope.id = user.id;
}, function(error) {
// unauthenticated error
});
};
$scope.isUserAuthenticated = function(){
Auth.isAuthenticated();
};
}]);
First of all you need to understand how cookies and sessions work in Rails.
From this article:
Rails uses a CookieStore to handle sessions. What it means is that all
the informations needed to identify a user's session is sent to the
client and nothing is stored on the server. When a user sends a
request, the session's cookie is processed and validated so rails,
warden, devise, etc. can figure out who you are and instantiate the
correct user from the database.
What this means is that on every request, Rails will look up at the session cookie, decode it and get something like
cookie = {
"session_id": "Value",
"_csrf_token": "token",
"user_id": "1"
}
At that point Rails knows that the current user has id=1 and can make a sql query. (Like current_user = User.find(1)).
When a user is logged in, a cookie is created, when the user is logged out - the cookie is destroyed. If Rails doesn't find a cookie or the cookie doesn't have information about the current user, devise will assume that the user is not logged in (current_user is nil)
Even if you login through ajax (to be particular it is through the 'angular_devise' gem in your case) the cookie is created. It is not stored on the server, but in the browser. (This is why if you are logged in one browser, you are not automatically logged in another browser) As you pointed out the library doesn't keep information who is logged in, and this is because the information is stored in a cookie and the library cannot decode the cookie without help from the server.
This is why you will have to make a call to get the current user if the user refreshes the page. (Sorry)
The way to get the current_user is very simple. This is the cleanest solution I found.
# application_controller.rb
def me
render json: current_user
end
# routes.rb
get "me" => "application#me"
// main.js
// I am not familiar with angular_devise lib but you get the point:
// this method fetches from server when myApp is initialized (e.g. on page reload)
// and assigns the current_user so he/she can be used by the app
myApp.run(["AuthService", function(AuthService) {
AuthService.getUserFromServer();
}]);
If you have to load data specific to the user, you will have to load the user first and then the data. Needless to say you will have to use promises.
TL;DR: You will have to ask the server
I am open for questions and comments.
I guess your problem is the refresh. The angular-devise lib is probably assuming you are in a SPA (Singe Page Application) so it should not refresh. With this assumption, angular-devise can store all the information in memory. When you refresh your page, you basically bootstrap the application from zero. And the request to server is probably issued by your code when application is starting. You probably call Auth.currentUser() somewhere on start of the application
Had same problem. Just use that gem
https://github.com/jsanders/angular_rails_csrf
You can also get rid of "protect_from_forgery" in your application controller, but this is very risky.
So I've got an question about authentication and have been wondering how other people might handle this situation. I'm currently running an Angular app that is built on a Rails API.
So far for authentication I have a form that does a post to the Rails side which logs the user in and then sends them back to the Angular app on success. Once the cookie is set and the user is logged in, I'm able to access a user.json file which contains all the User information one might expect (Id, username, roles, rights, etc). Since verification all happens on Rails, if the user logs out then this information is removed. So the two states look like so...
Logged in
{
id: 99384,
name: "Username",
url: "//www.test.com/profiles/Username",
timezone: null,
rights: [ ],
roles: [
"admin"
],
}
Logged out
{
error: "You need to login or join before continuing."
}
So far I've seen all these millions of different ways to do auth for Angular, but it seems like nothing fits this type of method. So my question is, since the server is handling all of the verification, is there a way to just check if they user.json file is empty (displaying the error message) and if it is send the Angular app to the Rails login page? Is there really any point messing with Cookies, Tokens, etc when I can base it all on the JSON file?
You are already using cookies - the server is setting them. What you have done is a fairly standard way of doing things.
To check the json file, you can do something like this stub shows in your controller:
app.controller('AppControl', function($scope, $http, $location){
// Get the JSON file.
$http.get('/path/to/json/file')
.then(response){
if(response.data.error){
// redirect to login
$location.path('login');
}
else{
$scope.user = response.data;
// your app code here.
}
})
.catch(function (error){
// unable to reach the json file - handle this.
});
});
Of course, you should really move this out into a service so you can re-use it, and also cache the data, rather than getting the user every time you change route/page, but this gives you a vague idea.
EDIT Example factory:
.factory('User', function( $http ){
// Create a user object - this is ultimately what the factory will return.
// it's a singleton, so there will only ever by one instance of it.
var user = {};
// NOTE: I am assigning the "then" function of the login promise to
// "whenLoggedIn" - your controller code is then very easy to read.
user.whenLoggedIn = $http.get('user.json')
.then(function(response){
// Check to see if there is an error.
if (response.data.error !== undefined) {
// You could be more thorough with this check to determine the
// correct action (examine the error)
user.loggedIn = false;
}
else {
// the user is logged in
user.loggedIn = true;
user.details = response.data;
return user;
}
}).then; // <-- make sure you understand why that .then is there.
return user;
})
Usage in the controller
.controller('ExampleController', function($scope, User){
// It's handy to have the user on the scope - you can use it in your markup
// like I have with ng-show on index.html.
$scope.User = User;
// Do stuff only if the user is loggedin.
// See how neat this is because of the use of the .then function
User.whenLoggedIn( function (user){
console.log(user.details.name + " is logged in");
});
});
Because it's on the scope, we can do this in the html:
<body ng-controller="ExampleController">
<h1 ng-show="User.loggedIn == null">Logging in..</h1>
<h1 ng-show="User.loggedIn == true">Logged in as {{ User.details.name }}</h1>
<h1 ng-show="User.loggedIn == false">Not logged in</h1>
</body>
Here is an example on plunker where this is working.
Note the following:
If the user is/was already logged in, when you inject the service in the future, it won't check the file again. You could create other methods on the service that would re-check the file, and also log the user out, back in, etc. I will leave that up to you.
There are other ways to do this - this is just one possible option!
This might be obvious, but it's always worth saying. You need to primarily handle authentication and security on the server side. The client side is just user experience, and makes sure the user doesn't see confusing or conflicting screens.
On my website, i want to allow for users, who is authorized with twitter, to post review on their own wall(page) in twitter, with my application.
I can do it for my page like this
$connection = new Twitter_TwitterOAuth(
$this->config->twitter->consumer_key,
$this->config->twitter->consumer_secret,
$this->config->twitter->token,
$this->config->twitter->token_secret
);
$connection->post('statuses/update', array('status' => $data['text'] . $data['name'] . "." . chr(13) . chr(10) . "More details " . $data['link'] .'?review='. $data['id']));
So what i need to do to post the same for another accounts ? i need to set account(where i want to post status with application) ID or i need to set other
$this->config->twitter->token,
$this->config->twitter->token_secret
with token and token_secret that i receive when user login with my application ?
Thanks for help.
for another account you should sleep(//someseconds); if you don't have a throttle in that library, then do the same for the other account. It looks like the account is yours, because in the code you posted I see so authorization from the user (callback). If my guess is right and you're using app authorization, you need to have authorized your own app with your other account, so ideally there should be no need to reset the tokens. If it doesn't work, try to reset them. If it still doesn't work, a trick is to have two applications (each account has an application, each account uses its own app to post). However, please bear in mind that a) Twitter counts the overall number of calls from your server and b) I'm not sure they like multiple accounts.
EDIT: if instead you need to post for users who have authorized your app and are currently online on your website, just store their tokens in a session (as Abraham Williams does in his library). In that case, you need to reset the variables to reflect the change of the tokens. Sample code, by Williams:
/* Get user access tokens out of the session. */
$access_token = $_SESSION['access_token'];
$access_token_secret = $_SESSION["access_token_secret"];
/* Create a TwitterOauth object with consumer/user tokens. */
$connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $access_token,
$access_token_secret);
$message ="your message";
$parameters = (array('status' =>$message));
$status = $connection->post('statuses/update', $parameters);
twitteroauth_row('statuses/update', $status, $connection->http_code, $parameters);
if ($http_code = 200){
// echo "Done!";
} else {
// echo "Oops!";
}