OpenID and OAuth using DotNetOpenAuth in ASP.NET MVC
I have an ASP.NET MVC application that I would like to have both public and private features. Typically this is done by implementing an authentication / authorization gateway to gain access to the private features. Previously I blogged on an implementation using the ASP.NET membership / role providers with an XML-based membership. This blog post will detail how to use OpenID and OAuth to provide an authentication layer.
- Authentication – You are who you say you are. The application knows your identity.
- Authorization – Based upon your identity (via Authentication), the application provides access to features. Public access is provided to base features. Private access to enhanced features.
Here is a screencast showing the implementation in action.
For more information on OpenID and OAuth check out these links:
OAuth-OpenID: You’re barking up the wrong tree if you think they’re the same thing
OpenID / OAuth Workflow
The web application starts by presenting an area that allows the user to select between various OpenID / OAuth providers. The following screenshot shows this area.
This provides a mechanism for the user to select one of the OpenId (OpenID, myOpenID, Google, Yahoo) or OAuth (Twitter) providers. Clicking on one of the providers starts the authentication process. Both OpenID and OAuth use a series of redirects to get the user authenticated. The key is that the authentication occurs on the provider’s site and that’s where the passwords are stored. Clicking one of the above providers results in a post to our application’s server. Our server then redirects to the provider’s server (after possible adding information to the post data). Here is a screenshot of the Twitter authentication page after the redirect to the Twitter provider.
Clicking the “Allow” button causes post to the Twitter servers and another redirect. This time back to our application’s server. This redirect includes information about you that is supplied by the OpenID / OAuth providers. At minimum the redirect supplies a ‘username’ back to the application.
Our application can then look up the ‘username’ in the database to determine if the user is allowed access. Users that are allowed access will have the above login area swapped out for a panel that provides access to features only available to authenticated users. The following screenshot is an example.
Now that we have covered the workflow, let’s take a look at some code. Since we have just looked at the presentation layer, let’s start with the ASP.NET MVC views.
Views, CSS and JavaScript
The logic to display either the login screen or the panel with features for authenticated users is in the Master Page. The following code simply looks to determine if the user is authenticated. If not, it inserts the login partial view. Otherwise, it inserts in the panel partial view.
<div id="login"> <% if (Request.IsAuthenticated) { %> <% Html.RenderPartial("_UserPanel"); %> <% } else { %> <% Html.RenderPartial("_Login"); %> <% } %> </div>
The panel partial view is provided by the following markup.
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %> <div id="user-panel-wrap"> <span class="left"><%= Html.Encode( Session["FullName"] ) %></span> <span class="right"> <ul> <li><%= Html.ActionLink("Add Truck", "Add", "Home") %></li> <li><%= Html.ActionLink("Log Off", "LogOff", "Account") %></li> </ul> </span> </div>
The above code inserts a ‘div’ containing two ‘spans’. The first ‘span’ is ‘floated left’ (via the CSS class left) and contains the user’s full name. The full name is stored in the ‘Session’ dictionary by the Controller (more later). The second ‘span’ is ‘floated right’ (via the CSS class right) and contains an unordered list of links that provide access to features available to authenticated users only. Be sure to mark these action methods with the ‘Authorize’ attribute otherwise users could directly navigate to the URL and access the feature.
The following CSS styles the panel partial view.
.left { float: left; } .right { float: right;} #user-panel-wrap { background-color: #000; margin: 10px 0; padding: 10px; color: #fff; text-align: right; overflow: auto; } #user-panel-wrap ul { display: inline-block; list-style: none; } #user-panel-wrap ul li { float: left; margin: 3px 10px; }
The login partial view markup is shown below.
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %> <div id="openid-wrap"> <form id="openid-form" method="post" action="<%= Url.Action("OpenId", "Account") %>"> <p>Registered users can <strong>view and add private trucks</strong>. Otherwise feel free to browse the <strong>public</strong> trucks in the list below.</p> <p>Sign in using one of the providers below.</p> <div id="openid-providers"> <ul> <li id="openid"></li> <li id="myopenid"></li> <li id="twitter"></li> <li id="google"></li> <li id="yahoo"></li> </ul> </div> <div id="other"> <h3>Your OpendID Provider</h3> <input name="openIdUrl" id="openIdUrl" type="text" /> <a id="signin">Sign In »</a> </div> </form> </div>
The outer ‘div’ wraps a ‘form’ area that contains two child ‘div’ elements. The first child ‘div’ contains an unordered list of providers. This will be styled up using CSS and an image sprite to render the button images. The second child ‘div’ provides an ‘input’ text element that allows the user to enter the URL to any OpenID provider. The following CSS styles this partial view.
#openid-wrap { background-color: #000; padding: 10px; color: #fff; margin-bottom: 10px; } #openid-wrap p { font-size: 18px; text-align: center; margin: 5px 140px 14px; line-height: 30px; } #openid-wrap form { margin: 3px; } #openid-wrap div#other { display: none; text-align: center; margin: 10px 40px; padding: 10px; color: #000; background-color: #ddd; border: 1px solid #888; font-size: 18px; } #openid-wrap div#other h3 { color: #333; font-weight: bold; margin: 0 0 3px 0; } #openid-wrap input { border: 2px solid #fba424; height: 36px; width: 500px; } #openid-wrap div#other a { background-color: #333; color: #eee; border: 1px solid #666; padding: 10px 15px; display: inline-block; } #openid-wrap div#other a:hover { background-color: #222; color: #fff; text-decoration: none; cursor: pointer; } #openid-providers { overflow: auto; text-align: center; } #openid-providers ul { display: inline-block; list-style: none; } #openid-providers li { margin: 10px; padding: 0; float: left; width: 150px; height: 50px; background: transparent url('../images/openid_sprite.png') no-repeat 0 0; } li#openid { background-position: 0 0; } li#myopenid { background-position: 0 -50px; } li#twitter { background-position: 0 -100px; } li#facebook { background-position: 0 -150px; } li#google { background-position: 0 -200px; } li#yahoo { background-position: 0 -250px; } li#openid:hover { background-position: -150px 0; cursor: pointer; } li#myopenid:hover { background-position: -150px -50px; cursor: pointer; } li#twitter:hover { background-position: -150px -100px; cursor: pointer; } li#facebook:hover { background-position: -150px -150px; cursor: pointer; } li#google:hover { background-position: -150px -200px; cursor: pointer; } li#yahoo:hover { background-position: -150px -250px; cursor: pointer; }
Towards the bottom the background image sprite for the provider buttons is set and individual offsets / hover effects are configured. Here is the image sprite that I am using.
The left column is the normal button faces and the right column provides a subtly different (maybe too subtle) hover image.
The glue that makes this view work is the following JavaScript (using jQuery of course).
<script type="text/javascript"> var providers = { myopenid: { action: '<%= Url.Action("OpenId", "Account") %>', url: 'http://myopenid.com' }, twitter: { action: '<%= Url.Action("OAuth", "Account") %>' }, facebook: { action: '<%= Url.Action("OpenId", "Account") %>', url: 'http://facebook.com' }, google: { action: '<%= Url.Action("OpenId", "Account") %>', url: 'http://www.google.com/accounts/o8/id' }, yahoo: { action: '<%= Url.Action("OpenId", "Account") %>', url: 'http://yahoo.com' } }; $("#openid").click(function () { toggleOther(); }); $("#signin").click(function () { $("#openid-form").submit(); }); $("li").not("#openid").click(function () { $("#other").hide(500); isHidden = true; var id = $(this).attr("id"); var provider = providers[id]; if (provider.action != undefined) { $("#openid-form").attr("action", provider.action); } if (provider.url != undefined) { $("#openIdUrl").val(provider.url); } $("#openid-form").submit(); }); var isHidden = true; function toggleOther() { if (isHidden) { $("#other").slideDown(500); } else { $("#other").slideUp(500); } isHidden = !isHidden; } </script>
The first bit of JavaScript creates a JSON object containing provider information. Each provider can have an ‘action’ and/or a ‘url’ member. The ‘action’ member defines the URL that the ‘form’ should post to when submitted. Notice there are different URLs for OpenID and OAuth. The ‘url’ member provides the URL to the OpenID provider.
The ‘toggleOther’ method (at the bottom) hides or shows the ‘div’ with the ID ‘other’ by using a slide up/down animation. The essence of the above JavaScript is to set the ‘input’ text box to contain the appropriate URL to the OpenID provider and then submit the form to the desired action method. This is done in the click handler for the ‘li’ elements and uses the JSON object.
Controller
Before discussing the action methods of the controller, you will need to get the DotNetOpenAuth library. Once you download it add a reference to it from your web application project. If you want to get twitter connected up be sure to also download the ‘TwitterConsumer’ and ‘InMemoryTokenManager’ implementations. These implementations are C# code files that you will need to add to your project as well.
I have named the controller ‘AccountController’ and the following code shows the constructor and private fields.
static private readonly OpenIdRelyingParty Openid = new OpenIdRelyingParty(); static private readonly InMemoryTokenManager TokenManager = new InMemoryTokenManager("consumerKey", "consumerSecret"); private readonly IFormsAuthentication _formsAuth; private readonly IIntKeyedRepository<User> _userRepository; private readonly IUnitOfWork _unitOfWork; private readonly ILogger _logger; public AccountController( IFormsAuthentication formsAuthentication, IIntKeyedRepository<User> userRepository, IUnitOfWork unitOfWork, ILogger logger) { _formsAuth = formsAuthentication; _userRepository = userRepository; _unitOfWork = unitOfWork; _logger = logger; }
The application uses Ninject as a dependency injection container (thus the missing parameterless controller constructor). Ninject will provide implementations of all the constructor parameters. References to these parameters are then tucked away into the ‘readonly’ fields.
The ‘OpenIdRelyingParty’ instance is an object implemented by the DotNetOpenAuth library that is used for the OpenID authentication. The ‘InMemoryTokenManager’ is used for OAuth authentication with Twitter. Before you can use Twitter as an OAuth provider, you must register your app with Twitter. As a result of this registration Twitter will issue a ‘consumerKey’ and a ‘consumerSecret’ to substitute as the ‘InMemoryTokenManager’ parameters.
From our JavaScript discussions remember that OpenID and OAuth are submitted to two separate action methods. First let’s look at the action method that handles OpenID authentication.
public ActionResult OpenId(string openIdUrl) { var response = Openid.GetResponse(); if (response == null) { // User submitting Identifier Identifier id; if (Identifier.TryParse(openIdUrl, out id)) { try { var request = Openid.CreateRequest(openIdUrl); var fetch = new FetchRequest(); fetch.Attributes.AddRequired(WellKnownAttributes.Contact.Email); fetch.Attributes.AddRequired(WellKnownAttributes.Name.First); fetch.Attributes.AddRequired(WellKnownAttributes.Name.Last); request.AddExtension(fetch); return request.RedirectingResponse.AsActionResult(); } catch (ProtocolException ex) { _logger.Error("OpenID Exception...", ex); return RedirectToAction("Login"); } } _logger.Info("OpenID Error...invalid url. url='" + openIdUrl + "'"); return RedirectToAction("Login"); } // OpenID Provider sending assertion response switch (response.Status) { case AuthenticationStatus.Authenticated: var fetch = response.GetExtension<FetchResponse>(); string firstName = "unknown"; string lastName = "unknown"; string email = "unknown"; if(fetch!=null) { firstName = fetch.GetAttributeValue(WellKnownAttributes.Name.First); lastName = fetch.GetAttributeValue(WellKnownAttributes.Name.Last); email = fetch.GetAttributeValue(WellKnownAttributes.Contact.Email); } return CreateUser(response.ClaimedIdentifier, firstName, lastName, email); case AuthenticationStatus.Canceled: _logger.Info("OpenID: Cancelled at provider."); return RedirectToAction("Login"); case AuthenticationStatus.Failed: _logger.Error("OpenID Exception...", response.Exception); return RedirectToAction("Login"); } return RedirectToAction("Login"); }
This action method is called twice. The first time by our form being submitted by the user. The second time is a call back (redirect) from the OpenID provider. The first two lines of the method determine which stage we are in. The code inside the ‘if’ statement is executed when the user submits the form. This code creates an OpenID request and redirects to the OpenID provider. Notice that we are requesting first and last names along with an email address. If the user has configured their OpenID provider to share this info it will be available to us.
The OpenID provider then redirects their response back to our action method. This time, there will be a value for the ‘response’ The code inside the ‘switch’ statement processes that response. If the user is authenticated, the username, first & last name and email are send to the following ‘CreateUser’ action method.
private ActionResult CreateUser(string userName, string firstName, string lastName, string email) { User user = _userRepository.FindBy(x => x.UserName == userName); if(user==null) { user = new User { UserName = userName, FirstName = firstName, LastName = lastName, Email = email }; return View("Create", user); } Session["UserName"] = userName; string fullName = user.FirstName + " " + user.LastName; Session["FullName"] = fullName; _formsAuth.SignIn(fullName, false); return RedirectToAction("Index", "Home"); }
This code checks to see if this user exists in the local database. If the user is not found, the ‘Create’ view is sent to the browser. This gives our application an opportunity to collect and validate the first & last name and email values. I will not show that view because it becomes application specific as to what additional information you collect from each user. I will also not show the action method that the ‘Create’ view posts back to.
If after the OpenID authentication the user was found in the database, the ‘username’ and ‘fullname’ are stored in the ‘Session’ cache, the user is signed in (cookie set) and a final redirect back to the ‘Index’ action method of the ‘Home’ page. This results in showing the panel that provides access to features only available to authenticated users.
The OAuth action methods are shown below. The ‘OAuth’ action method is called when the user submits the form. This uses the DotNetOpenAuth library to redirect (on the Channel.Send method) to the OAuth provider. Notice this code explicitly configures the callback URL.
public ActionResult OAuth(string returnUrl) { var twitter = new WebConsumer(TwitterConsumer.ServiceDescription, TokenManager); var callBackUrl = new Uri(Request.Url.Scheme + "://" + Request.Url.Authority + "/Account/OAuthCallback"); twitter.Channel.Send(twitter.PrepareRequestUserAuthorization(callBackUrl, null, null)); return RedirectToAction("Login"); } public ActionResult OAuthCallback() { var twitter = new WebConsumer(TwitterConsumer.ServiceDescription, TokenManager); var accessTokenResponse = twitter.ProcessUserAuthorization(); if(accessTokenResponse!=null) { string userName = accessTokenResponse.ExtraData["screen_name"]; return CreateUser(userName, null, null, null); } _logger.Error("OAuth: No access token response!"); return RedirectToAction("Login"); }
The OAuth provider then redirects to the specified callback URL (‘OAuthCallback’ in this case) where the response is processed. A similar process is followed as in the OpenID case. Authenticated responses use the ‘CreateUser’ action method (same as for OpenID and shown above).
Summary
Integrating OpenID and OAuth into your application is made very easy by using the DotNetOpenAuth library. There are a number of services popping up that provide a clean implementation of OpenID / OAuth authentication that you can pay to include in your project. The advantage is that they will maintain the compatibility with OpenID / OAuth as it evolves and are able to provide you statistics on access to your site. Otherwise, using the DotNetOpenAuth library and wiring things up your self is not that bad. If you find the DotNetOpenAuth library beneficial and if you feel so inclined to give back, they provide a way to donate on their page.
hi,
should you post the complete code ?
thanks.
Hi,
Great work! just one question if you don’t mind. How do you generate "TwitterConsumer.ServiceDescription" ?
Thanks,
Andi
Hi again,
Don’t worry about my previous question. I found the answer.
Thanks,
Andi
Great post, but isn’t it possible that two or more users might have the same username? I think you should be using the OpenID ClaimedIdentifier to uniquely identify users.
Good catch. I have updated the blog to use the ‘ClaimedIdentifier’ property. On the same note, if I was supporting multiple OAuth providers (currently Twitter is the only one) I would have a potential username conflict.
nice post. keep it up
Thanks for your post and information ((o;
What about facebook? Can you take the same approach?
It does look like facebook support OpenID. Here is a link: http://developers.facebook.com/blog/post/246
Bob
Thanks, I am writing small website and this post was really helpful for me!
Very nice post Bob. So far the best resource I’ve found on wiring dotnetopenauth into MVC. Big thanks.
Could you either post full sample code OR have working samples within the library please.
GE
Gareth, I have been meaning to do so for quite some time. I finally have published the project that contains this code to codeplex. Check it out here: http://gpsnerd.codeplex.com. Please let me know if you have any questions.
Great work. Thank you.
I am trying to integrate DotNetOpenAuth into my project.
Trying to get authentication work first and then move on to implement a authorization provider.
MVC is great, I just start learning it.
But do you have any idea how this work using webform? I think the “control” should be pretty much alike. Anyway, thanks again.
this post is greate, been looking for a dotnet samples for this logins.
thx you very much 🙂
Hi Bob,
I have spent a week on dotnetopenauth now.
I have some progress on the consumer side but the service provider side, I just don’t have any clue how DNOA is going to help me.
Could you kindly give me some pointers on how to implement service provider with DNOA?
Write three .ashx to handle “Request token”, “Access token”, “Authorize”, is that it?
I want to show my thanks to this writer for rescuing me from this dilemma. Just after researching through the the web and getting notions which are not helpful, I figured my life was well over. Being alive minus the answers to the issues you’ve fixed by means of your good report is a serious case, and ones that might have adversely affected my career if I hadn’t come across your web page. The talents and kindness in maneuvering a lot of stuff was very helpful. I am not sure what I would have done if I had not discovered such a step like this. I can also now look ahead to my future. Thank you so much for your high quality and results-oriented guide. I will not be reluctant to suggest your web site to anybody who wants and needs guidance on this issue.
i’m looking for server to server authentication how can i do that ?
Thanks very much for taking the time to post this article and source code, you’re a bloody legend 🙂
Hey,
I have been trying to follow this… now I got it working for twitter and it gives me the username and user_id. But I want more details than that… and I have been trying to implement the openid stuff but I don’t know the open id url for twitter or even if it has one. How would I go about requesting extra information for twitter?
Thanks.
Mikey
Thanks a lot !!
Hi.. I want to know what will be the procedures if I’m going to add the facebook login to this. Please help me
I found my own way.. Anyone interested? Please let me know.
If you managed to get it working for Facebook I’m interested!
Very good post. I just stumbled upon your weblog as well as wished to say that I have
certainly loved surfing around your blog posts.
In any case I’ll be signing up to your feed and I
hope you write once more soon!
Hi!
Nice article. I have integrated oauth for twitter. All is working well. But when I call “twitter.Channel.Send(twitter.PrepareRequestUserAuthorization(callBackUrl, null, null));” and it redirects to twitter website and there is displayed a message “Oh dear, something has gone awry. Invalid user name or password.”. I noticed that url is “https://api.twitter.com/oauth/authorize”. It must contains the token like “https://api.twitter.com/oauth/authorize?teknstringxxxxx”. Am I missing some thing in InMemoryTokenManager.cs(I copied it from the samples of dotnetopenauth).
However, when I signin then I am redirected and screen_name is fetched.
Can anyone help?
Regards
It’s truly a great and useful piece of info. I am happy that you just shared this useful information with us. Please stay us up to date like this. Thank you for sharing.
Pretty! This has been a really wonderful post. Thanks for supplying this
info.
Hello just wanted to give you a quick heads
up. The text in your post seem to be running off
the screen in Opera. I’m not sure if this is a format issue or something to do with internet browser compatibility but I figured I’d post
to let you know. The design and style look great though!
Hope you get the issue solved soon. Many thanks
This is my first time visit at here and i am really impressed
to read all at one place.
It’s actually a great and helpful piece of information. I’m happy that
you shared this helpful information with us. Please stay us informed like
this. Thanks for sharing.
Hey! I just wanted to ask if you ever have any trouble with hackers?
My last blog (wordpress) was hacked and I ended up losing months of hard work due to
no back up. Do you have any solutions to prevent hackers?
Simply wish to say your article is as astounding. The clearness for your submit is just spectacular and that i can suppose
you are knowledgeable on this subject. Fine with your permission let me to
grasp your RSS feed to keep up to date with impending post.
Thank you one million and please carry on the gratifying work.
I love the efforts you have put in this, regards for all the great
posts.
great points altogether, you just won a new reader.
What might you suggest in regards to your put
up that you made a few days ago? Any positive?
Hello! I know this is kinda off topic but I was wondering which blog platform are
you using for this site? I’m getting sick and tired of WordPress because I’ve had
issues with hackers and I’m looking at alternatives for another platform.
I would be awesome if you could point me in the direction of a good platform.
Hey there! I’ve been reading your site for a long time now and finally got the bravery to go ahead and give you a
shout out from Dallas Tx! Just wanted to mention keep up the great work!
Good web site! I truly love how it is easy on my eyes and the data are well written.
I am wondering how I could be notified when a new post has been made.
I have subscribed to your RSS which must do the
trick! Have a nice day!
Awesome article.
You can certainly see your skills in the article yoou write.
The sector hopes for more passionate writers like you who are
noot afraid too say how they believe. All the time go after your heart.
Hi there! I know this is kind of off-topic however I needed to ask.
Does building a well-established blog like yours require
a large amount of work? I’m brand new to blogging however
I do write in my diary daily. I’d like to start a blog so I can share my personal experience
and feelings online. Please let me know if you have any recommendations or tips for
new aspiring bloggers. Appreciate it!
I do not even know howw I stopped up right here, howeverr I assumed
this publish used to be good. I ddo not realize who you might be howeveer certainly you are going to a famous blogger when you aree not already.
Cheers!
Thanks ffor the marvelous posting! I definitely enjoyed reading it, you hapen to be a
great author. I will always bookmark your blog and definitely will come back from now
on. I want to encourage continue your great work, have a nice
morning!
When I originally commented I clicked the “Notify me when new comments are added” checkbox and now each time a comment is added
I get several e-mails with the same comment. Is there any way you can remove people from that service?
Cheers!
Thanks for ones marvelous posting! I seriously enjoyed reading it, you will be a
great author. I will be sure to bookmark your blog and will often come back someday.
I want to encourage that you continue your great work, have a nice day!
whoah this blog is wonderful i love studying your articles.
Keep up the great work! You recognize, a lot of persons are searching round for this information, you can aid them greatly.
I blog frequently and I seriously appreciate your information. This article has really peaked my
interest. I will bookmark your blog and keep checking for new information about once per week.
I subscribed to your RSS feed too.
Hello there, just became alert to your blog
through Google, and found that it’s truly informative.
I’m going to watch out for brussels. I’ll be grateful if you continue this in future.
Many people will be benefited from your writing. Cheers!
If there is still no image, please close your anti-virus software, and then try step 1
& 2 again. The original listing price of this digital camera is
$179 so you get to save more up to $60. Nje kamera shpines qese eshte nje zgjedhje e mire per shume lloje te fotografeve qe jane gjithmone ne levizje , por ju
nuk keni per te blere nje te tille qe eshte shume e shtrenjte per te marre tiparet qe
kane rendesi me per ju.
Yes! Finally someone writes about rag เถื่อน.
Undeniably believe that that you stated. Your favourite justification appeared to be at the net the simplest factor to take into accout of.
I say to you, I definitely get irked whilst other folks consider issues that they plainly don’t recognize about.
You controlled to hit the nail upon the highest and outlined out the entire thing without having side-effects , folks could take
a signal. Will likely be again to get more. Thank
you
Its such as youu read my mind! You seem tto understand a
lot about this, such as you wrote thee book inn it or something.
I feel that you can do with a few p.c. to power the message home a little bit, however other than that, this is wonderful blog.
An excellent read. I’ll certainly be back.
Thanks for ones marvelous posting! I actually enjoyed
reading it, you may be a great author.I will be sure to bookmark your blog and will
eventually come back in the foreseeable future. I want to encourage you continue your great job,
have a nice evening!
Amazing blog! Is your theme custom made or
did you download it from somewhere? A theme
like yours with a few simple tweeks would really make my
blog jump out. Please let me know where you got your theme.
Thank you
However, this is often good once the visitors are more prone to be serious potential customers as opposed
to lookie-lous who will be just cruising the internet.
You employ strategies in background design and
colours that invite customers to buy. The two most important
factors which help a web site in gaining a better visibility are namely content
and authority.
Good information. Lucky me I recently found your site by accident
(stumbleupon). I’ve book-marked it for later!
Hello! I know this iss kinda off topic nevertheless I’d figured I’d ask.
Would you be interested in trading links or maybe guest
authoring a blog article or vice-versa? My website addresses
a lot of the same topics as yopurs and I think we could greatly benefit from each other.
If you happen to bbe interested feel free to send me an e-mail.
Ilook forward to earing from you! Awesome blog by the way!
Fantastic web site. Plenty of helpful information here.
I’m sending it to several buddies ans additionally sharing in delicious.
And of course, thanks in your effort!
I’m gone to convey my little brother, that he should also
visit this website on regular basis to take updated from hottest news update.
To this day, the Chinese still use pearl powder as a skin whitener and a cosmetic, as
do many other people around the world. Burn three pieces of charcoal in a metal container,
and place three medium size garlic cloves on top of them.
When we humans put things into perspective we will soon realize that science is no different from metaphysics and spirituality.
hello there and thank you for your information – I have definitely picked up anything new from right here.
I did however expertise some technical issues using
this website, since I experienced to reload the web site many times previous to I could get it to load properly.
I had been wondering if your web hosting is OK? Not that
I am complaining, but sluggish loading instances times will very frequently
affect your placement in google and can damage your high-quality score
if ads and marketing with Adwords. Well I’m adding this RSS to my e-mail and
can look out for much more of your respective fascinating content.
Ensure that you update this again soon.
You really make it seem so easy with your presentation but I
find this matter to be really something that I think I would never understand.
It seems too complex and extremely broad for me. I’m looking
forward for your next post, I will try to get the hang of it!
It’s a pity you don’t have a donate button! I’d definitely donate to this excellent blog!
I suppose for now i’ll settle for bookmarking and adding your RSS feed to my Google account.
I look forward to fresh updates and will share this site with my Facebook group.
Talk soon!
It is not my first time to go to see this web page, i am browsing this web page
dailly and get good information from here every day.
Good day very cool website!! Man .. Excellent ..
Superb .. I’ll bookmark your site and take the feeds also? I am happy
to find a lot of helpful information right here within the post, we
want work out extra strategies in this regard, thanks for
sharing. . . . . .
Hiya! Quick question that’s entirely off topic.
Do you know how to make your site mobile friendly?
My site looks weird when viewing from my apple iphone.
I’m trying to find a template or plugin that might be able to resolve this issue.
If you have any suggestions, please share. Thanks!