Writing an OAuth Provider in PHP
Earlier I wrote about consuming an OAuth service. This kind of blog posts is reasonably common on the internet. What seems to be less common is posts on providing an OAuth service. Today I hope to share some light on the topic by offering an extensive post on exactly that. As well as my first article the provider will use PECL's OAuth package.
Contents
Usually I don't offer a contents section in an article, but this one is quite long. To give you an idea what to expect, here it is:
- The code in this article
- OAuth, why and what?
- Getting started, offering a consumer key and secret
- Setting up the first endpoint
- The consumer handler
- The timestamp and nonce handler
- Outputting the request token
- Authorising the request token
- Getting an access token
- An actual API call
- Omissions
- References
The Code in this article
As I already wrote, this will be a long article. I was torn (how's that for a heavy term ;) ) between offering as much code as possible and not making this article any longer than strictly necessary. I also wanted to do more than offer some snippets of code which have to be glued together.
As a solution to these problems I decided to work out a full example and make it public through github. It is a work in progress but you can find it here. I will still provide code snippets during this article, but the repository offers a full and working (after setup) example. All code in this article will be based on the example folder that can be found there. If something in the example isn't working, I created a bug. In that case, please contact me trough the contact form or in the comments.
OAuth, why and what?
If you don't know about OAuth at all, I suggest you start by reading my article on consuming an OAuth service which offers a short explanation on the concept. It gives an explanation of a typical use case as well. After that it would be wise to skim the OAuth specification here.
Since you took my advice (you did, didn't you), you're now up to speed with OAuth. Lets start our journey through OAuth providing land.
Getting started, offering a consumer key and secret
The very first we need to facilitate is a location where OAuth consumers can apply for a consumer key and secret. The code for this isn't too complicated and my example's implementation doesn't offer a UI but it will do for this article's sake.
$Consumer = new OAuthConsumerModel(Configuration::getDataStore()); $Consumer->setConsumerCreateDate(time()); $Consumer->setConsumerKey(OAuthProviderWrapper::generateToken()); $Consumer->setConsumerSecret(OAuthProviderWrapper::generateToken()); $Consumer->save(); echo "Consumer key: " . $Consumer->getConsumerKey() . "
Consumer secret: " . $Consumer->getConsumerSecret();
In a real world application you would ask a potential consumer a whole lot of questions. The least you'll want is contact information. Remember, once granted a consumer key and secret, a consumer can interact with your service in an almost seamless way. The consumer will be assumed trustworthy by your users, you'll have to make sure that that's justified.
One small note on the Configuration and Provider static functions you may have noticed in the code snippet above. The ProviderWrapper functions will be talked about in detail. The Configuration::getDataStore() is my current solution to have one place where a datastore (mysql in my case) connection is defined. Ideally I would use dependency injection only to pass the datastore around, but because of the callbacks (talked about later in this article) this isn't possible.
Setting up the first endpoint
The first endpoint we need to offer to a consumer is the request token endpoint. The part that executes all the necessary checks and outputs the actual token information looks like this:
$Provider = new OAuthProviderWrapper(OAuthProviderWrapper::TOKEN_REQUEST); $response = $Provider->checkOAuthRequest(); if ($response !== true) { echo $response; exit; } try { $Provider->outputRequestToken(); } catch (ProviderException $Exception) { echo $Exception->getMessage(); }
As you can see the Wrapper class does a lot of work for us. This is way too magical for this article but it demonstrates how much can be abstracted away.
Ignore the checkOAuthRequest function for now, its purpose and workings will get clear later on in this article. We'll look at the OAuthProviderWrapper's constuctor first. Upon construction the wrapper initialises an OAuthProvider class and sets callback functions disregarding the constant the wrapper is created with.
public function __construct($mode) { $this->Provider = new OAuthProvider(); $this->Provider->consumerHandler(array($this,'consumerHandler')); $this->Provider->timestampNonceHandler(array($this,'timestampNonceHandler')); if ($mode == self::TOKEN_REQUEST) { $this->Provider->isRequestTokenEndpoint(true); //enforce the presence of these parameters $this->Provider->addRequiredParameter("oauth_callback"); $this->Provider->addRequiredParameter("scope"); } ... }
As you can see the two callback methods are consumerHandler and timestampNonceHandler. We also need to tell the OAuthProvider that we're dealing with a request token. If we don't a third callback is expected, but more about that later.
You'll also notice the two calls to addRequiredParameter. This tells the OAuthProvider object we expect scope and oauth_callback to be present. If they're not, any request to our request token endpoint should be considered invalid.
The consumer handler
The first of the two callback functions we specified in the previous example is the consumer handler. This will check if the consumer information that is provided with the current request leads to a known consumer. This function is expected to return either of the following PECL OAuth constants: OAUTH_CONSUMER_KEY_UNKNOWN, OAUTH_CONSUMER_KEY_REFUSED or OAUTH_OK. Part of the full function is seen below:
public static function consumerHandler($Provider) { try { $OAuthConsumer = OAuthConsumerModel::loadFromConsumerKey($Provider->consumer_key, Configuration::getDataStore()); } catch (DataStoreReadException $Exception) { return OAUTH_CONSUMER_KEY_UNKNOWN; } $Provider->consumer_secret = $OAuthConsumer->getConsumerSecret(); return OAUTH_OK; }
So what happens? A factory method is used to load a Model class. If the consumer key is unknown, the model will throw an exception and an OAuth constant is returned. If the model is found, the Provider that we received trough callback is informed of the corresponding secret. The Provider will need this to verify the signature later on as it only received the consumer key from the Consumer. Finally we let the Provider know all is OK!
You might be wondering what the OAUTH_CONSUMER_KEY_REFUSED could be used for. This constant informs the consumer that its consumer key and secret are (temporarily) not considered valid anymore. This could happen if a provider supports blacklisting or limits request per unit of time. Both options are not found in this example at this moment.
The timestamp and nonce handler
The second callback function checks two things: the timestamp and the nonce (the setter method's name is kind of a give away :) ). Both the nonce and the timestamp are required by the OAuth specification. The nonce and timestamp are a barrier against replay attacks. The nonce is basically there to prevent someone listening to a conversation between a consumer and provider from storing and repeating a succesful request.
An attacker can't forge a request since he lacks the shared secrets, but a successfully call to an API which results in data could be eavesdropped upon and made over and over again. With a number in place that changes every request (thus changing the signature which can only be created with the shared secret which is not in the attackers possession), eavesdropping is rather useless. The request has been sent already and won't be valid a second time.
This following quote comes from the RFC and tells us more about the nonce and timestamp:
A nonce is a random string, uniquely generated by the client to allow the server to verify that a request has never been made before and helps prevent replay attacks when requests are made over a non-secure channel. The nonce value MUST be unique across all requests with the same timestamp, client credentials, and token combinations.
To avoid the need to retain an infinite number of nonce values for future checks, servers MAY choose to restrict the time period after which a request with an old timestamp is rejected.
With that in mind look at the following code:
public static function timestampNonceHandler($Provider) { if (time() - $Provider->timestamp > 300) { return OAUTH_BAD_TIMESTAMP; } if (OAuthNonceModel::nonceExists($Provider->nonce, Configuration::getDataStore())) { return OAUTH_BAD_NONCE; } $OAuthNonce = new OAuthNonceModel(Configuration::getDataStore()); $OAuthNonce->setId($Provider->nonce); $OAuthNonce->setNonceConsumerKey($Provider->consumer_key); $OAuthNonce->setNonceDate(time()); $OAuthNonce->save(); return OAUTH_OK; }
As you can see we ignore all requests older than 5 minutes. This quite a large window, but we must keep in mind that servers are most likely not synchronised at the exact same time. If the timestamp checks offers no problems, we check to see if the nonce already exists in our database. If it does we invalidate the request, if it doesn't we create a new model and save it. Return values consist once again of the OAuth package predefined constants
Outputting the request token
Untill now we've seen two handlers being defined but how are they called? For that a seperate function exists which does little more than call the same function at the OAuthProvider. This function is called at the endpoint after construction (see above if you forgot).
public function checkOAuthRequest() { try { $this->Provider->checkOAuthRequest(); } catch (Exception $Exception) { return OAuthProvider::reportProblem($Exception); } return true; }
The function call above will also result into a check for the validity of the received signature and the presence of required parameters (oauth_callback and scope). Luckily for us those happen internally and are not setup by ourself. We're now at a point where a request has come in and all we needed to check has been checked. We therefore can issue a new request token. This function looks like this:
public function outputRequestToken() { $token = OAuthProviderWrapper::generateToken(); $tokenSecret = OAuthProviderWrapper::generateToken(); $RequestToken = new OAuthRequestTokenModel(Configuration::getDataStore()); $RequestToken->setToken($token); $RequestToken->setTokenSecret($tokenSecret); $RequestToken->setTokenDate(time()); $RequestToken->setTokenConsumerKey($this->Provider->consumer_key); $RequestToken->setTokenCallback($_GET['oauth_callback']); $RequestToken->setTokenScope($_GET['scope']); $RequestToken->save(); echo "oauth_token=$token&oauth_token_secret=$tokenSecret&oauth_callback_confirmed=true"; }
Nothing really exciting is happening here. We use the generateToken function from the OAuthProvider to do our hard work, store some data in a model and return a string per the rfc's specifications (see the end of this paragraph). The endpoint exits its executing after this call and we've finished the first part of our OAuth dance with the consumer!
Authorising the request token
Now the consumer has received a request token and secret from us the second part of the dance starts. The consumer must redirect its user to our service's authorisation page. The user must be made very much aware of what he is about to consent to. A provider should double check identy by logging in once again. Even when a user is already logged in, it is advisible to do so. See what the RFC has to say on this:
However, the server MUST first verify the identity of the resource owner.
When asking the resource owner to authorize the requested access, the server SHOULD present to the resource owner information about the client requesting access based on the association of the temporary credentials with the client identity. When displaying any such information, the server SHOULD indicate if the information has been verified.
When a user does login and grants permission we will lookup the user id and store it our request token model. Secondly a verification code is generated and also stored at the request token model. Then the user is redirected back to the callback URL that was provided in the initial request. Below you see the code starting from the part where the user is already looked up in the database:
if ($row['user_password'] != $_POST['user_password']) { echo "You hacker, be gone!"; exit; } $verificationCode = OAuthProviderWrapper::generateToken(); $RequestToken->setTokenVerificationCode($verificationCode); $RequestToken->setTokenUserId($row['user_id']); $RequestToken->save(); header( 'location: ' . $RequestToken->getTokenCallback() . '?oauth_token=' . $RequestToken->getToken() . '&oauth_verifier=' . $verificationCode );
As you can see this isn't the largest and hardest part of the dance so that leaves us with time for questions :) What is the use of the verification code you ask? The verification code is sent back to the consumer to ensure that the user granting access is the same user that will be coming back to complete the last part of our OAuth dance. Now we've issued a request token and the user has authorised it, there is only one more step before our API can be used.
Getting an access token
The last part of our dance evolves around the access token. Our wrapper at this endpoint is constructed with a different constant as before making sure the correct code in the constructor will be reached:
$this->Provider = new OAuthProvider(); $this->Provider->consumerHandler(array($this,'consumerHandler')); $this->Provider->timestampNonceHandler(array($this,'timestampNonceHandler')); if ($mode == self::TOKEN_REQUEST) { ... } else if ($mode == self::TOKEN_ACCESS) { $this->Provider->tokenHandler(array($this,'checkRequestToken')); } ...
Earlier I mentioned the existence of a third handler. This is the tokenHandler which is required for any non-request token request. Inside the request token handler we can perform checks before issuing an access token, so lets see what our trusted RFC has to say on this topic:
The server MUST verify (Section 3.2) the validity of the request, ensure that the resource owner has authorized the provisioning of token credentials to the client, and ensure that the temporary credentials have not expired or been used before. The server MUST also verify the verification code received from the client.
This is how that looks in code:
public static function checkRequestToken($Provider) { $DataStore = Configuration::getDataStore(); //Token can not be loaded, reject it. try { $RequestToken = OAuthRequestTokenModel::loadFromToken($Provider->token, $DataStore); } catch (DataStoreReadException $Exception) { return OAUTH_TOKEN_REJECTED; } //The consumer must be the same as the one this request token was originally issued for if ($RequestToken->getTokenConsumerKey() != $Provider->consumer_key) { return OAUTH_TOKEN_REJECTED; } //Check if the verification code is correct. if ($_GET['oauth_verifier'] != $RequestToken->getTokenVerificationCode()) { return OAUTH_VERIFIER_INVALID; } $Provider->token_secret = $RequestToken->getTokenSecret(); return OAUTH_OK; }
If the the checkOAuthRequest in the access token endpoint doesn't fail the outputAccessToken function is called. This looks a lot like the outputRequestToken function. It has one addition however, the request token is no longer necessary so it is deleted:
public function outputAccessToken() { $token = OAuthProviderWrapper::generateToken(); $tokenSecret = OAuthProviderWrapper::generateToken(); $AccessToken = new OAuthAccessTokenModel(Configuration::getDataStore()); $AccessToken->setAccessToken($token); $AccessToken->setAccessTokenSecret($tokenSecret); $AccessToken->setAccessTokenDate(time()); $AccessToken->setAccessTokenConsumerKey($this->Provider->consumer_key); $AccessToken->setAccessTokenUserId($RequestToken->getTokenUserId()); $AccessToken->setAccessTokenScope($RequestToken->getTokenScope()); $AccessToken->save(); //The access token was saved. This means the request token that was exchanged for it can be deleted. $RequestToken->delete(); echo "oauth_token=$token&oauth_token_secret=$tokenSecret"; }
Hurray we have access token! Or more precisely: our consumer does. At this point there is only one small thing left. Securing our API with OAuth.
An actual API call
At this point the consumer has all the data it needs to make an authorised call to our API and get the personalised and authorised data. If you check out the example you'll notice there is a user_messages table. Let see how our very simple API would look if it wishes to open up that data to the world:
$Provider = new OAuthProviderWrapper(OAuthProviderWrapper::TOKEN_VERIFY); $response = $Provider->checkOAuthRequest(); if ($response !== true) { echo $response; exit; } $userId = $Provider->getUserId(); $sql = "SELECT * FROM `user_messages` WHERE `user_id` = '" . $userId . "'"; $result = Configuration::getDataStore()->query($sql); //Token is valid, lets output something $returnValue = ""; while ($row = $result->fetch_assoc()) { $returnValue .= "" . $row['message_text'] . ""; } $returnValue .= ""; echo $returnValue;
The code above once again constructs an OAuthProviderWrapper, but this time with the TOKEN_VERIFY constant. This tells the constructor our current token handler is the checkAccessToken function. Indirectly this is called trough the checkOAuthRequest function. If authorisation is successful we output some data. The output part isn't interesting, but lets take a look at the checkAccessToken as our final code example:
public static function checkAccessToken($Provider) { $DataStore = Configuration::getDataStore(); //Try to load the access token try { $AccessToken = OAuthAccessTokenModel::loadFromToken($Provider->token, $DataStore); } catch (DataStoreReadException $Exception) { return OAUTH_TOKEN_REJECTED; } //The consumer must be the same as the one this request token was originally issued for if ($AccessToken->getAccessTokenConsumerKey() != $Provider->consumer_key) { return OAUTH_TOKEN_REJECTED; } $Provider->token_secret = $AccessToken->getAccessTokenSecret(); return OAUTH_OK; }
If the token can't be loaded, or if the consumer isn't the one we expect the token is rejected, but otherwise there is nothing wrong. Basically this all there is to it. We've just checked the last request we needed to check and data has been provided to the consumer. Please read the omissions paragraph to see what is missing or can be improved, but otherwise be happy you've read through all these words :)
Omissions
While reading along you might have noticed stuff missing once or twice. You should definitely take a look into blacklisting consumers and you must take a look at limiting access by a unit of time. Also we let the consumer specifically pass us a scope. Yet we did nothing with it. Depending on the amount of different datamodels you want to open up through your API you should also take a look into that direction. Obviously it would be brilliant if this article had covered this as well, but it is long enough already. Besides, there must be something left for you to figure out :)
References
While writing the proof of concept of the OAuth provider I read Rasmus Lerdorfs post on providing OAuth and while rewriting that proof of concept into a structured piece of code I stumbled accross Lorna Jane Mitchells series on the topic. Though an RFC is not always fun to read, that too was invaluable.
An extremely well written and comprehensive guide to general OAuth can be found here. Finally the documentation to the OAuth PECL package (scarsely present, certainly as compared to many other parts of the PHP.net site) can be found here.
Comments
-
I have a network of forums, with users who are active on several of them. I would like to give them one ID and allow access to my whole network.
So by providing an OAuth service, you wouldn't have to use Twitter, you can actually roll you own authentication that could be used across several site? -
Thanks
-
Hey Dave,
OAuth is not the best solution to the problem you just described. You want some sort of Single Sign-on (SSO) solution. OpenID might be what you really want as far as I can deduce from your reaction.
Just for the record, OAuth and OpenID are not the same. This article explains why quite well.
However if you're sure you want to use OAuth, I think other people do it too. I have read of people using OAuth as authentication meganism at least, but I haven't seen or used this actively. If examples for that use Twitter or Google for instance, they could indeed be replaced by your own OAuth provider perfectly well.
I'm sorry I can't be of more help, but I've never implemented an OpenID server or used OAuth in that way before :)
-
Hi,Freek Lijten.I love this blog and I want to ask you some questions about OAuth.
I'am developing an android application,and I have a small website,which can provide some RESTful API.I want to add OAuth to these API.And I need 2-legged OAuth,because I want user could directly input username and password,by the android app, and finish the OAuth flow.
My question is,how should I build this 2-legged OAuth interface by php?Thank you.
-
Hey Eric,
First of all: Thanks! :)
I understand you're going to let users enter usernames and passwords inside the application you are building. If your users should input a username and password, why would you bother going trough the hassle of OAuth at all? One of the main points of OAuth is avoiding exactly that. It is easier and probably better to simply provide a secure URL using SSL/TLS (https) so users can safely transfer their login data over the internet.
I don't know of a lot of 2-legged OAuth providers, but I have worked with Google's API's and what they call 2-legged OAuth before (see this link to their documentation).
The way it works is as follows. Basically the whole authentication cycle resulting in an access token is not used. An access token is not a requirement when accessing a protected resource. Simply having the consumer key and secret is enough to make a signed and successful request to a protected resource. This means that they shouldn't be exposed to anyone who does not need to know about it.
Using this technique in a mobile app sounds like a bad idea to me, because the mobile app's source code is essentially available to the public. By decompiling the source code and filtering out the consumer key and secret, everybody can access the data.
Having said that, I don't think there is an official specification for 2-legged OAuth at all. I believe other parties have different definitions for what 2-legged OAuth actually means or should mean. As I said before I think you'd better look for a simpler solution in this specific case anyway.
Cheers,
Freek
-
Thank you Freek!
You are right, I don't need Oauth in my project:) HTTPS is enough.
Thank you again for your patient reply!Looking forward to your new blog.
-
I am trying to get a request token from my provider using this URL:
MYSERVER/oauth/request_token?oauth_callback=http://localhost/client/callback.php
My php client is running locally on my mac's web server. The array comes back basically empty from the provider, and I get this string from OAuth:
oauth_problem=parameter_absent&oauth_parameters_absent=oauth_consumer_key%26oauth_signature%26oauth_signature_method%26oauth_nonce%26oauth_timestamp
I understand that this means I'm missing the consumer key, signature, nonce, and timestamp. However, I don't think the client should be providing this. My conclusion is that there is something wrong with the provider side of things. In my provider, I call these two functions when asking for a request token:
/** * This function check the handlers that we added in the constructor * and then checks for a valid signature */ public function checkRequest(){ /* now that everything is setup we run the checks */ try{ $this->oauth->checkOAuthRequest(); } catch(OAuthException $E){ echo OAuthProvider::reportProblem($E); $this->oauth_error = true; } } //AND public function generateRequestToken(){ if($this->oauth_error){ return false; } $token = sha1(OAuthProvider::generateToken(20,true)); $token_secret = sha1(OAuthProvider::generateToken(20,true)); $callback = $this->oauth->callback; Token::createRequestToken($this->consumer, $token, $token_secret, $callback); return "authentification_url=".$this->authentification_url."&oauth_token=".$token."&oauth_token_secret=".$token_secret."&oauth_callback_confirmed=true"; }
So, I should be getting the information back via the return statement there at the bottom. Any ideas why it is asking for the key, sig, nonce, and timestamp, and why it won't return the request token, signature, and authentication url, etc?
Thanks so much!
Wade
-
Sorry about the formatting of the code above. It did not look that way in the submit box. If it is not readable, I would be glad to email you something better formatted. Thanks for your help either way.
Wade
-
Dave,
can you please write php oauth provide for Salesforce as i am not able to write wsdl which will invoke oauthprovider php class. -
For anyone having trouble with the OAuth Provider class make sure you install the OAuth PECL extension.
I used this link to install it on CentOS 6 and I got everything working fine now.
Regards,
Hanley Hansen -
Hi Freek, thanks for this guide...I have some problems and i hope you can help me! the code work well when i try to run Oauth on my local server that run with xampp for MAC, but if I put the code on remote server Debian with apache2, I have a little problems.. the get_request_token is very slow, if i try to call the file get_request_token.php i will wait around three minutes before show the login form! also in the next step when i try to login with the correct credentials.
I saw also in the apache error log this error: Access denied for user 'www-data'@'localhost' (using password: NO) in .../OAuth/lib/Configuration.php on lineSo i think it could have lost the database connection.
On the remote server i have https and ssl, I tried to disable https and ssl but i have the same problems.
For install oauth on debian with php5.x i used this commands:
apt-get install php5-dev php-pear
apt-get install make libpcre3-dev
apt-get install libcurl3-dev
pecl install oauth
vi /etc/php5/conf.d/oauth.iniAdded the follow line to php.ini: extension=oauth.so
For mac i followed this guide: http://www.sumardi.net/2011/06/04/installing-oauth-extension-in-xampp-for-mac-os-x/
Have you an idea? Do you know if on Debian i have to set more options on the server or i have to install other libraries?
-
What's up, I read your blog regularly. Your humoristic style is
witty, keep up the good work! -
Thank you for this library and tutorial. I've actually managed to get my first access token ever!
I'm still stucked on the next step : how do I define which specific request I want to make? My API structure is like :
api/users/
api/users/{{id}}
api/users/?sort=[joined_on=desc]/range=-10;/I guess for the server side I just have to use your api.php file's code into my api controller but how about the consumer api_call.php file? How to add parameters into the URL so that I can get it back on the provider side using GET?
I can see the project has not been updated for years, though I hope you still come around to give some advice.
Cheers!
-
Is it Oauth1 implementation or Oauth2?
-
hey freek,
this is a relatively old blog,
but everything is still working and it is a good tutorial for beginners like me who are very much confused by the oauth2-crap...
thanks -
It's a good tutorial for beginners on how implementing Oauth2 thanks
-
Hi ,
We have implemented it on my local server whenever i am running get_access_token.php its taking too much time in generate token any ideas sometime its giving
string(37) "making the request failed (dunno why)"
NULL
bool(false)NULL
After large amount of time.
Thanks
Sujit -
Hi there, I discovered your web site by way of Google at the
same time as searching for a similar topic, your web site came up, it appears
to be like good. I have bookmarked it in my google bookmarks.Hello there, simply turned into aware of your blog through
Google, and found that it's really informative. I'm gonna be careful for brussels.
I will appreciate when you continue this in future. Many folks can be benefited out of your writing.
Cheers! NBA 2K19 -
Asking questions are genuinely fastidious thing if you
are not understanding something entirely, but
this piece of writing provides nice understanding even. -
When someone writes an paragraph he/she retains the image of a
user in his/her mind that how a user can know it. Therefore that's why this paragraph is outstdanding.
Thanks! -
Does your site have a contact page? I'm having a tough time locating it but, I'd like to send you an e-mail.
I've got some suggestions for your blog you might be interested in hearing.
Either way, great website and I look forward to seeing
it expand over time. -
Your style is very unique compared to other folks I've read stuff from.
Many thanks for posting when you've got the opportunity, Guess I
will just bookmark this page. -
As the admin of this website is working, no question very rapidly it will be famous, due to
its quality contents. -
It's not my first time to visit this site, i am visiting this web page
dailly and obtain fastidious data from here daily. -
Heya are using Wordpress for your site platform? I'm new to the blog world but
I'm trying to get started and create my own. Do you need any html coding expertise to make your own blog?
Any help would be really appreciated! -
You need to take part in a contest for one of the most useful
sites on the net. I will highly recommend this site! -
Spot on with this write-up, I actually feel this site needs
a lot more attention. I'll probably be returning to
read more, thanks for the information! -
Link exchange is nothing else except it is just placing the
other person's website link on your page at proper
place and other person will also do similar in support of you. -
Write more, thats all I have to say. Literally,
it seems as though you relied on the video to make your point.
You clearly know what youre talking about, why throw away your intelligence on just posting videos to
your blog when you could be giving us something enlightening to read? -
My coder is trying to persuade me to move to .net from PHP.
I have always disliked the idea because of the expenses.
But he's tryiong none the less. I've been using WordPress on several websites for about a year
and am nervous about switching to another platform.
I have heard excellent things about blogengine.net.Is there a way I can import all my wordpress content into it?
Any kind of help would be greatly appreciated! -
This is very interesting, You are a very skilled blogger.
I've joined your rss feed and look forward to seeking more of your great post.
Also, I've shared your website in my social networks! -
Hi there friends, its impressive paragraph about educationand entirely explained, keep it up all the time.
-
Awesome article.
-
I am not sure where you're getting your information, but
good topic. I needs to spend some time learning more or understanding more.
Thanks for fantastic information I was looking for this information for my mission. -
Thanks to my father who shared with me on the topic of
this blog, this blog is really remarkable. -
Hello, after reading this remarkable article i am also glad to share my know-how here with colleagues.
-
Hi mates, good paragraph and pleasant arguments commented at this place,
I am in fact enjoying by these. -
Excellent post. I used to be checking constantly this blog and I am inspired!
Extremely useful information particularly the ultimate phase :) I deal with such information much.I used to be seeking this certain info for a long time.
Thank you and best of luck. -
Thank you for some other excellent article. The place else may
anybody get that kind of information in such
an ideal manner of writing? I've a presentation next week, and I'm on the look for such info. -
Hello, I log on to your blog regularly. Your story-telling style is witty, keep up the
good work! -
Thanks for finally writing about >Writing an OAuth Provider in PHP -
Freek Lijten <Loved it! -
Hello there, I discovered your site by the use of Google at the same time
as searching for a related subject, your site got here up, it seems good.
I've bookmarked it in my google bookmarks.
Hi there, simply changed into aware of your weblog
through Google, and located that it's truly informative.
I'm going to be careful for brussels. I will appreciate if you proceed this in future.
Lots of other folks can be benefited out of your writing.Cheers!
-
Sweet blog! I found it while searching on Yahoo News.
Do you have any tips on how to get listed in Yahoo News?
I've been trying for a while but I never seem to get there!
Appreciate it -
Getting the most effective DNA boards is not a stroll in the park. However, Evolv has attempted to make the procedure simpler.
-
I actually wanted to make a small comment to be able
to express gratitude to you for those stunning hints you are posting at this site.My time intensive internet look up has now been paid with pleasant facts
and techniques to go over with my colleagues. I 'd state that that many of us readers actually are quite
blessed to dwell in a good website with so many
lovely people with great solutions. I feel very much happy
to have used the website page and look forward to so many more amazing minutes reading here.
Thank you again for a lot of things. -
The Do 18s crossed the South Atlantic 73 instances.
-
Search engines count hyperlinks as votes of high quality.
-
Hi there just wanted to give you a brief heads up and let
you know a few of the images aren't loading correctly.I'm not sure why but I think its a linking issue.
I've tried it in two different browsers and both show the same
results. -
Hanfu, with the name oriented from the Chinese meaning 'Han people's garments', encompassing all kinds as well as styles of traditional apparel worn by the Han Chinese.
The Han Chinese trace an usual origins to the Huaxia, a name for the preliminary confederation of farming tribes living along the Yellow River. The term Huaxia represents the cumulative Neolithic confederation of farming people Hua and Xia that settled along the Central Plains around the center and lower reaches of the Yellow River in northern China.
Hanfu, as its definitions, birthed at the start of the background of Han ethic. Consequently, it has the longest background among all conventional Chinese apparel. -
Excellent, what a web site it is! This web site presents helpful data to us,
keep it up. -
For most recent news you have to visit world-wide-web and on world-wide-web I found this
website as a best website for hottest updates. -
After checking out a handful of the articles on your web
page, I really like your technique of blogging. I saved as a favorite it to my bookmark site list and will be checking back soon. Please check out my
web site as well and let me know how you feel. -
Thanks for your marvelous posting! I really enjoyed reading it, you might be a great author.I will be
sure to bookmark your blog and will come back at some point.
I want to encourage one to continue your great job, have a nice afternoon!