Integrating OpenID in an ASP.NET MVC Application using DotNetOpenAuth
2010-09-10 14:09
585 查看
The#1requestIgotontheCodePaste.netsite-whichprovidesaquickandeasywaytopublishandsharecodesnippetspublicly-hasbeentoimplementOpenIdforauthenticationratherthanthecustomusername/passwordloginthat’sbeeninuseupuntilnow.OpenIdisacentralizedlogin/authenticationmechanismwhichhandlesauthenticationthroughoneormorecentralizedOpenIdproviders.TheseprovidersliveontheWebandareaccessedthroughaforwardingandcallbackmechanism–youloginattheprovider’ssiteandarethenreturnedtotheoriginalstartingUrlwithanauthorizedusertokenthatuniquelyidentifiesthatusertotheoriginalsite.
TobehonestIhadn’twarmeduptoOpenIduntilrecently.InfactIhadtobeconvincedbyuserrequestsontheCodePaste.netsitetotakealookatintegration.Nowthatit’sthereIthinkitisaprettysweetwaytogoalthoughIthinktheconceptislostontheaverageuseruntilOpenIdbecomesmorewidespread.Italsodidn’thelpthattheoriginalimplementationsandproviderswereprettycrappyandtheprocessonmostsitestosignupactuallywasreallyslowandclunky.ThathaschangedabitwithbetterinterfacesthatarefasterandmanymoresitesareusingOpenIdandmanymoreprovidersexist.
AnotherpainpointisthatsupportingstandardsforretrievingprofileinformationlikeSimpleRegistration(SREG)isnotwidelysupported.Providersarereturninginformationinavarietyofdifferentformatsandmostdon’treturnprofileinformationatallwhetheryouapproveornotwhichisreallylame.Youpresumablyownthedataonanyserviceyou’vegivendatatosowhythefussofsharingitifyouapproveofsharing?Anyway,don’tcountongettingfullcontactinformationfromOpenIdforawhile.Someproviderswillgivesomeinformation,otherswon’tgiveany–soyourapplicationhastobeabletohandlebothscenarios.
ForthesiteIchoseDotNetOpenAuth–afterdoingalittlebitofresearchitseemedtomethatthiswasthemostactivedevelopmentprojectthatisalsomostfeaturerich.DotNetOpenAuthdoesmuchmorethanOpenIditalsosupportsoAuth(anon-interactiveservicebasedcounterparttoOpenId)whichisanotherthingonmylisttoimplementfortheAPIportionofthesite.DotNetOpenAuthisfairlycomprehensiveandincludessupportspecificallyforWebapplications–bothMVCandWebFormswithcontrolsandhelpersprovidedaswellasMVCspecificactionmethodstohandleredirectionandreturnroutingwhichisanicetouch.
Thetoolmakestheactualroutingandhandlingoftheresultsfairlyeasy,buttheintegrationprocessneverthelessisnotcompletelytrivial.ThereasonforthisisthatmostapplicationswillneedtohandlebothloginsaswellasattachinganOpenIdaccounttoanexistinguserprofileofsomesortandthiscangettrickybecauseoftheHTTPGETbasedcallbackinterface.TheactualOpenIdcallsandcallbacksarequitesimple–theactualuserinterfaceandhookuproutinesthoughendeduptakingmealotlongerthanIhadexpected.
TheloginformdisplaysbothloginsforOpenIdandthetraditionalusernameandpassword:
Figure1–LogginginwithOpenId
IneededtoleavetheoldlogininformplacesoexistinguserscanloginandattachtheirexistingaccountstoOpenId.AlsosomefolksmayjustnotwanttogotheOpenIdroute.Sobotharesupportedonthisform.
OpenIdvalidationstartsbyprovidingaOpenIdUrl.TheUrldependsontheproviderandyoucanpickupthatUrlfromyourprovider’sOpenIdinformationpage–hereisalistofafewcommononesfromtheOpenIdsite.Ialsoprovidedacoupleoficonbuttonsforthemostlikelyculprits–MyOpenId,GoogleandYahoo.Anyothersneedtobetypedin.BetweenGoogleandYahooyouprobablyhaveanaccount.There’salsomyOpenIdwhichisoneoftheearlyproviderswhichispopular,andwithityouuseaurllikehttp://rickstrahl.myopenid.comtologon.TheYahoo(http://yahoo.com/)andGoogle(https://www.google.com/accounts/o8/id)Urlsarethesameforeverybody,sothoselinkscanactuallybeauto-fireddirectlybytheclickontheicon,whilethemyOpenIdentryrequiresenteringthenamefirstandmanuallyclickingthebutton.
OncetheLoginbuttonisclicked(orautomaticallyfired)theprocesstakesyoutotheprovider’ssite.ForexampleifyouuseGoogle,you’llseeaGoogleLoginscreenifyouaren’talreadysignedintoGoogleandyouareaskedtologin.Thepagewilltellyouthesourcesitethat’srequestingtheloginandpossiblywhichprofileinformationisrequested.Onceyouloginyouarethenredirectedhenceyoucame–backtothesameurlwhereyoucamefrombydefault.Thiscanbeoverridden,butinanMVCapplicationyou’llmostlikelywillwanttocomebacktothesameURLtoredisplaythelogin(orregistration)datawithsomeindicationthatyouarenowloggedin.
IfyouarealreadyloggedintotheOpenIdprovider(whichisusuallythecase)theloginoperationiscompletelytransparent–thepageisre-routedtotheOpenIdprovidersiteandimmediatelyreturnsbacktoyoursiteataUrlthatisspecifed.Thisusuallyhappensprettyfastthroughabunchofredirects–fromyoursitetotheOpenIdproviderandfrotheOpenIdproviderbacktoyoursite–sootherthansomeextradelaytimeyoudon’tevenseeanythinghappening.Itjustlookslikeyou’regoingdirectlytoyournextpageintheapp.
WhenthepagereturnsafteraloginitincludesencodedauthenticationinformationonthequerystringthatbecomesavailabletotherequestthathandlestheOpenIdlogin.SpecifiallyDotNetOpenAuthcanreadtheauthenticationtokenandanyprofileinformationthatwasreturnedifanywasrequestedandreturned.
Onceloggedinmyprofiledisplaythenreflectstheaccountassociationlikethis:
Figure2–LoggedinwithOpenIdandviewingtheProfile
Theapplicationstorestheuser’sopenidaspartoftheprofileandsoit’seasytodisplaythisinformation.TheOpenIdrelationshipcanbeunlinkedandtheaccountcanthenbeassignedtoanotherOpenIdorausernameandpasswordhastobeprovidedifnoOpenIdisavailable.AsImentionedtheactualOpenIdauthenticationprocessisprettysimplebutsomeoftheissuessurroundingitlikeattachinganddisconnectingaccountsfromOpenIdisalittlemorecomplicated–I’lltalkaboutthatbelow.
IfIgototheprofilepagewhentheaccountisnotassociatedwithanOpenIdthepagehasanoptiontologinandeffectivelyassociatetheaccountwiththenewOpenId:
ThesamepageisalsousedfornewregistrationswhichisusefulinordertoprovidethenameandemailactuallyfromtheOpenIdprovider(assupported–notethatgoogleandyahoodonotprovidethisdatainaformatthatDotnetOpenAuthextractsautomatically).
I’llstartwiththesigninformbecausethat’ssimpler.Thisrelatestofigure1intheimagesequenceabove.TheOpenIdloginareaofthepageiswrappedintoitsownHTMLformthatkicksofftheprocessing.Thelayoutforthisblocklookslikethis:
TheimageiconsfireasmallJavaScriptroutinethatcreatestheOpenIdurlsthatareinjectedintothetextbox:
YoucanaddtothiswithotherURLsandiconsasyouseefit.Istuckwiththosethreebecausetheyarethemostcommonthatpeoplewillhaveaccountsonbutthatmaychange.AllothersOpenIdUrlscanbetypedinbyhand(whichisnotsosmooth).
WhentheLoginbuttonisclickedandtherequestissubmitteditfiresintoanOpenIdLogOnactionmethodofthecontroller.ThecontrollerhastoacceptbothPOSTandGEToperations.POSTisfromtheinitialbuttonclick,theGETwilloccurwhentheOpenIdproviderfiresbacktheresponseandprovidesthedataviathequerystring.
AsmentionedI’musingDotNetOpenAuthherewhichisasinglelargeDLLyoucandropintoyourBINfolder.Ithandlesalltheheavyliftingofcreatingtheredirectlinkandcrackingtheresponsequerystringextractingtheauthenticationtoken.
Theprocessinvolvestwosteps:MakingtheoriginalrequesttotheOpenIdprovider(whichredirects)andhandlingtheresponsethatcomesback.Here’sthecode(whichisprettyclosetothesamplecodeprovidedinoneofthedemos):
ThecodeusestheOpenIdRelyingPartyclasstohandletheprocessingofthelogin.Thefirststepoccurswhenthebuttonisclickedandthere’snoproviderresponsetoprocess.Thiscode:
varreq=openid.CreateRequest(Request.Form["openid_identifier"]);
returnreq.RedirectingResponse.AsActionResult();
firesoffthetherequest.Theopenid_identifieristheOpenIdUrltheuserenteredintotheformandit’spassedtoDotNetOpenAuthtostartformulatingarequestforauthenticationatthatUrl.
Thecodeabovestartstheredirectionsequencegoingtotheproviderandbacktothecurrentpage-sinceIdidn’tspecifyanotherUrltoreturntobydefaulttherequestreturnstothecurrentpage’surl.
AsmentionedI’musingMVCsoandasyoucanseeDotNetOpenAuthconvenientlyincludesanAsActionResult()methodthatcreatesredirectresponseinaproperMVCstyleresponsewhichisverythoughtfulforconsistency.YoucanalsoretrievetheURLdirectlyandfireitonyourownalthoughIcan’tseewhyyouwould.Ifyou’reusingWebFormsorahandlerthereareothermethodsonRedirectingResponsetofiretheredirectsforyou.
NextthebrowserisredirectedtotheOpenIdproviderwhereyoueitherareaskedtologinorifyoualreadyareloggedinyouareauthenticatedandimmediatelyreturnedtothecurrentpage.WhentherequestreturnsitisnowaGEToperationandwestartbackoutatthetopofthesameControlleraction.
Thistimearound:
varresponse=openid.GetResponse();
willnotbenull–thereturnURLincludesabunchofnewencodedquerystringvalues.GetResponse()looksforthoseandbasedonthatcreatestheresponseinstanceandthecodecanbranchintothegreaterifblock.Therequestwilleitherbeauthenticated,canceledorfailed.Failurecanbeanynumberofthingssuchasinvalidlogininformation,ortimingoutorsomesortoftamperingwiththeURL.Bothcancelandfailureoperationsarehandledbysimplyredisplayingthecurrentviewwithanerrormessage.
Ifauthenticationsucceededwecanretrieveauniqueidentifierthatidentifiestheuser’sproviderchoiceandwe’reguaranteedatthispointthatthisuserwasauthenticatedthroughOpenId.Yay!
AlthoughyounowknowtheuserisauthenticatedASP.NETknowsnothingaboutthisauthenticationyet,sothenextstepistosetaFormsAuthenticationtickettoauthenticatetheusertotheapplication.ThisprocessisnodifferentthandoingamanuallogininanyotherASP.NETapplicationwithFormsAuthentication.
InCodePaste.netIstoreauserrecordintheticketsoIhavesomebasicuserinformationavailabletomyapplicationoneachhitwithouthavingtousesessionstorageorretrievingthisinfooneveryrequestfromthedatabase.Thecodedoesthis:
whereIssueAuthTicket()lookslikethisdoingstandardFormsAuthenticationticketassignment:
Oncetheauthticket’sbeenissuedtheapplicationsimplyredirectstoanotherpageintheapplication(thenewsnippetpageinthiscase).
BTWjustforclarfication,theuserStateobjectcontainslogictoeasilypersisttoandfromstringsothisobjectiseasilyretrievedatthebeginningofarequestandstoredonupdates.InmybasecontrollerIdo:
TheprocesstoauthenticateusingOpenIdisprettystraightforward.Nosurpriseshere,really.AllweneedtohaveissomeconditionallogictorouteforredirectinitiallyandpickuptheresultwhentherequestcomesbackandhandletheauthenticationandcreationofanewFormsAuthenticationticket.Whentherequestreturnsandismarkedasauthenticatedyoucansafelyassumetheuserisvalid.Becauseofthemessageencryptionandtimedsecuritytokensthedataissafeandnothijackable.
TheHTMLlayoutinFigure3isnearlyidenticaltotheHTMLlayoutontheloginformexceptthere’ssomeconditionallogictodisplaytheinputboxorthecheckboxandmessagethatidentifiesthecurrentOpenIdassociation.
NoticethathereweneedtokeeptrackoftheactiveUserifany.Sinceyoucanattach/detachfromanexistingprofilewedohavetoknowwhichuserwe’redealingwith.Thissoundseasyenoughbutrememberthatyoucan’tuseplainPOSTdataforthisbecausethepagegoesofftheOpenIdproviderandcomesbackwithaGETthatjustholdstheresultvalues.Thismeanstoredisplaythepagetheentirepagehastobere-renderedbasedonthemodel.Todothiswe’llneedtokeeptrackoftheuseridfortherequestsequence.
Thisbaseprocessisverysimilartotheloginprocessandusesthesametwostagesofrequestsendingandreturn.FirstcapturethevaluefromahiddenformvariableandthenstoreitintheSessionobjecttopassbetweenthetworequests.Here’swhattheopenidattachment/detachmentcodelookslike:
Therearetwomethodshere–oneasanactualendpointandanotherthatcanbecalledwithanexistingresponsepassedtoit–thisisusefultoautomaticallyforcealoginthatdoesn’texistdirectlyintotheregistrationformviacode.ADotNetAuthresponsecanonlybeparsedonceorelseit’sconsideredinvalidandsopassinginanexistingresponsewasarequirementtohandlethissortof‘manual’routing.ForthesimplecallbackscenarioyoucanjustuseasimpleControllerendpointmethod.
AsinthelastexampletheOpenIdRelyingPartyisusedtohandletheactualrequestsemanticsofsendingtheredirectandreceivingthedata.Thispartoftheprocessreallydoesn’tchange.However,howyoudealwiththedatareceivedandabortscenariosaddsasignificantamountofadditionalcodecomparedtothepreviousloginonlyexample.
Ifthere’snoOpenIdresponse,thecodefirstchecksforunlinkrequests.ForthisitneedstheidtoloadaninstanceofthebusinessentityandupdateitbyremovingtheOpenIdfromit.ThepageisthenredisplayedagainusingtheIDasthekey.
OtherwisetherequestissentofftotheOpenIdprovider.TheproviderthenreturnsandagaintheOpenIdresponsewillnowbeset.OnsuccessthecodetriestoretrievetheClaimsRequestvalues.Claimsrequestissentwiththeinitialrequesttoasktheservertoreturnvaluesfromtheuser’sprofile.ForCodePaste.netI’minterestedinfullnameandemailasoptionalresults.NotethatsomeOpenIdonlyprovidersuseacommonformatcalledSREGtoreturnvalues.myOpenDnsdoesasdoseveralotherofthededicatedservices.However,thisstandardsisonlysketchilysupportedbythebigproviders.GoogleforexamplerequiresthatyouDemanddataandthenwillonlyreturntheemailaddress(whichseemsironicgiventhat’sthemostprivateitemintheprofile).Yahoodoesn’treturnanything–accordingtotheirOpenIdpagetheyonlyshareprofileinformationwithcertainsitestheydeemappropriate.SREGhoweverisgainingmomentumsoit’slikelythatallproviderswillevenutallysupportSREG.
InDotnetOpenAuththerequestfor‘claimdata’ishandledlikethis:
//RequestadditionalProfile
varreq=openid.CreateRequest(Request.Form["openid_identifier"]);informationwithClaimsRequest
varfields=newClaimsRequest();
fields.Email=DemandLevel.Demand;
fields.FullName=DemandLevel.Demand;
req.AddExtension(fields);
YoucanRequestorDemandclaims.IntheoryDemandmeansthatiftheprovideroruserrefusestoprovidetherequestedkeystherequestfails.InrealitythoughproviderslikegoogleignoreRequestcommandsandsoDemandisoftenrequiredtoretrieveinformation.
ToretrievetheclaimsdatausesGetExtension<T>():
//Retrievecustomprofileinformationifavailable
varclaim=response.GetExtension<ClaimsResponse>();
stringemail=null,fullname=null;
if(claim!=null)
{
email=claim.Email;
fullname=claim.FullName;
}
Justrememberthatthere’snoguaranteethesevaluesareset.
Theotherissueinthiscontrollerdealswithstatemanagementoftheuser.Whentheusercomestothepagetohookupwehavestateonthepageforthatparticularuser,butwhenwere-directtotheOpenIdprovidersiteweloosethatstate.SointhiscasetheuseridneedstobetrackedandI’musingaSessionobjectforthis.Ihaven’thadmuchneedforsessioninMVCapplicationsbutthisisonecasewhereIdon’tseeawayaroundthisunfortunately.Beforetherequestissenttheuser’sidisstoredinaSessionobject.Whentheauthenticatedrequestreturnsthesessionidisretrievedandusedtolookuptheuser’sbusinessobjectwhichistheupdatedwiththeopenididentifierandoptionallytheprofileinformationifany.Therestofthebulkycodedealswithafewchecksonthedatareturnedandisolatingwhichdatatoupdate.
AsanyWebbasedcallbackmechanismthecodeisbulkybecauseyouhavetoeffectivelyroutetheresponsesinsideyourcodeandyouendupwithsomecodethatcouplesWebandbusinesslogicwhichisalwaysugly.IrememberfightingsimilarissuesyearsagowithPayPalintegration.ProcesseslikethesearecumbersomebecausetheyaresocloselytiedtotheactualWebrequest–theControllerActioninthiscaseanddon’tprovideforaneasywaytoabstractandwrapintobusinesslogic.That’sthepenaltyyoupayforadistributedsysteminmanysituationsandthisisnodifferent.
TherearealsoothersolutionoutthereforOpenIdintegration.RobConeryhasbeenmuckingaroundandtryingtoconvincemetotryRPXwhichusesamaninthemiddleapproachforhostingOpenIdauthentication.TheybasicallyprovidetheloginAPIviaapopupwindowandyouthenretrievetheauthenticationdataviaabackendservicecall.ButthisrequiresyetanotherproviderinthemiddlethatyoutalktothroughaproprietaryAPI.Itmighthelpisolatingtheauthlogic(servicecallbackvs.acontrolleraction).Butit’snotforme–butforsomeofyouthismightbeinterestingtocheckoutespeciallyifyoulikethewaytheRPXlooks.It'lldefinitelybealesscodeapproach.
Ihopethoughthatthisarticleisusefultosomeofyou.IcertainlywishIwouldhavehadsomethingtolookatwhenIstartedlookingatOpenId.There’salotofhighlevelposturingstuffouttherethattalksaboutthebenefitsandgoalsofopenIDbutpreciouslylittlecodethatactuallyshowstheprogressionofstepsincontextthatexplainsOpenIdflowonahighlevelalongwithanimplementation.
Ialsowanttothank
DotNetOpenAuthLibrary
.NETlibraryforprocessingOpenId,oAuth,InfoCardandvariousotherauthenticationAPIs.Verycomprehensiveandthoughtfulfunctionalityalthoughdocumentationisabitonthesparseside.Excellentsupportandfeedbackfromthedeveloperwho’sreallypassionateaboutthislibraryandcloselymonitorsandlistenstofeedback.
CodePaste.netSourceCode
ThisarticleisbasedonthecodeonCodePaste.netandifyoulikeyoucancheckoutthesourcecodefromtheSubversionrepository.
PostedinASP.NETMVC
FeedbackforthisWeblogEntry
#IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byDotNetKicks.comSeptember17,2009@9:18pm
You'vebeenkicked(agoodthing)-TrackbackfromDotNetKicks.com
#re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byWilliamKapkeSeptember18,2009@12:09am
Thereisdefinitelyalackofusefulinformationoutthere.ThanksforgettingthisASP.NETwalkthroughtogether.
I'vebeenhopingsomeonewouldputtogetherawellmadevideooftheprocessoutliningtheTECHNICALdetails.Suchasananimatedflowofthedata.
Isn'tthereaspecouttherethatistryingpushusing"username@myhost.com"insteadof"httt://username.myhost.com"?Ibelieveitworksbyfirstsendingarequestouttomyhost.comandgettingaurlpatternback-whichittheninjectstheusernameinto.(IwishIknewwhereIreadaboutthat)
Ithinkusingtheemailpatternwillhelpwithadoption.
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byRickStrahlSeptember18,2009@1:23am
@William-I'mnotsurethatavideowalkthroughwouldbeallthathelpful-thisisthesortofthingwhereyouneedsomecodetolookatandcopyandpasteandafairbitofit.
Astotheurls-theyareactuallyfairlysimpleformostproviders.TheyareURLsbecausethoseareuniquewhereasemailaddressesareflakeythatwayespeciallywithemailaddresseschanging.ItlookslikemostprovidersaregoingtherouteofusingasimpleURLonly.YahooisjustYahoo.comandtheyfigureouttoroutetotheopenidproviderbasedonthequerystringdatacomingin.IseereallynobenefitofhavinganysortofuseridentificationwiththeURL-ifyou'renotloggedinyouhavetoprovideyourcredentialsanywayanditmakestheurleasiertoworkwithifit'sthesameforallusers.Onceyou'reloggedinwithanOpenIdprovider(whichifyouuseitismostofthetimetheycanpullupyourprofilebeforeyoulogonandauto-fillwhatevernon-privateinformationonthesignonform.
ThesimplerthebetterandthesimplestisthesameURLforeveryoneforeachprovider.
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byRaxitSeptember18,2009@3:38am
ThanksRickforsharingthis.OnethingIamnotclearwithgoogleopenid.IwanttodisplayUserEMailoncetheusersuccessfullygetsauthenticatedandreturntomywebsiteverysimilartowhatyouhavedonebutitreturnstokenizedURLlikewww.google.com/accounts/o8/id?id=AItOawmtAnQvSXGhRTkAHZ...whenIaccesse.Response.FriendlyIdentifierForDisplay.
UponsearchingforawhileIgotthisthreadhttp://stackoverflow.com/questions/1355292/friendly-name-from-google-using-openidwhichsaysgoogledoesnotreturnfriendlyIdentifiers.LetmeknowifIammissingsomethinghere.
FYI:IamusingcontrolsprovidedwithDotnetOpenAuthi.eOpenIdLogin,OpenIdButton.
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byAndrewArnottSeptember18,2009@5:52am
HiRick,
Firstofall,thanksforyourpost.That'sprobablythemostdetailedandthoughtoutoneI'veseenyet.
Butyouhaveasmall(big)problem.You'reusingIAuthenticationResponse.FriendlyIdentifierForDisplaytomakesecuritydecisions.ItshouldONLYbeusedtodisplaytheidentifiertotheuser(thusthelongname;).YoushouldbeusingIAuthenticationRespones.ClaimedIdentifierastheformsauthticketusernametokeepyoursitesecureagainstidentityspoofingattacks.
#IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth-RickStrahl
byDotNetShoutoutSeptember18,2009@7:01am
Thankyouforsubmittingthiscoolstory-TrackbackfromDotNetShoutout
#ВнедряемOpenIDвприложениеASP.NETMVC
byprogg.ruSeptember18,2009@9:57am
Thankyouforsubmittingthiscoolstory-Trackbackfromprogg.ru
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byRickStrahlSeptember18,2009@11:51am
@Raxit-The'friendlyidentifier'isdeterminedbytheOpenIdproviderandyouhavenocontroloverwhatthatis.Googlereturnsalongandnastystringforthiswhichisfine-youdon'treallyneedtodisplayitanywhere(ijustdidbecauseitwasashowcasemoreorless).Ideallytherealinformationthatyouwanttostore/keep/holdontoisprofileinformationbutasIpointedoutinthearticlethisinformationisofmixedqualityandnotguaranteedtobeprovided.
FriendlyIdentifierinthecodeaboveiswhatDotnetOpenAuthreturns.Ibelieveifsomethingismissingit'lljustreturntheuniqueurlreturnedwhichisrequiredaspartoftheOpenIdconversation.Someothersiteslikemyopenid.netreturnname.myopennet.idasthefriendlyidentifierwhichisnicerforsure.
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byRickStrahlSeptember18,2009@11:58am
@Andrew-thanksforthefeedback.NotsureIfollowthough.I'mstoringthefriendlyidintheprofileonlyasalookupwhichisONLYcheckedafteraAuthenticationStatus.Authenticatedcallbackthatshouldbeguaranteedtobesecure(otherwiseyourlibrarywouldn'treturnthiscodeanditwouldbepointless,right?:-}).
Theformsauthenticationticketincludes*nothing*atallrelatedtoOpenId.IbasicallyonlyauthenticatethroughOpenIdandonceI'mdoneIissueatotallyunrelatedformsauthenticationticketthathasnodirectconnectiontotheOpenIdidentifieroranythingelseforthatmatter.PlainFormsAuthticket.
SincetheonlywayauthenticationoccursisthroughtheAuthenticationStatus.AuthenticatedcallbackIdon'tseehowthiscouldbeinsecure.ThefriendlyIdismerelyusedasthelookupkeythatmapstheOpenIdaccountandtheuserrecordonmysite.
UsingtheticketretrievedfromOpenIdwouldn'tworkinthisscenariobecausethatticketwillchangewitheachlogin-sonocontinuity.ThewayitisnowiftheFormsAuthticketexpiresortheuserlogsout,nexttimetheyhavere-authenticatewithOpenIdandgothroughthesameassignmentcycleagain.
Ifyouseeaholeherepleasecouldyouclarify,becauseIdon'tseeit?
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byMikeSeptember18,2009@1:57pm
Rick
YouCANgetemailinfofromgoogle...youhavetoenablethisintheweb.configandaddasanextensiononyourrequest:
request.AddExtension(newClaimsRequest
{
Email=DemandLevel.Require,
});
It'sdocumentedonthedotnetopenauthsiteprettywellandIjustimplementedthisforMonoRaileasily.
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byAndrewArnottSeptember18,2009@2:01pm
Rick,
YouarecreatingtheauthticketusingtheOpenID"friendlyidentifier":
...newFormsAuthenticationTicket(1,userState.Name...
This,andthefactthatyouusethefriendlyidentifierforlookupiswhat'swrong.YouMUSTuseClaimedIdentifierforusernamelookup,whichinturnmeansyoureallyshoulduseitfortheformsauthticket'susernameaswell.TheFriendlyIdentifierForDisplayisNOTguaranteedtobeuniqueforeachuserastheOpenIDClaimedIdentifieris.Forinstance,https://somebody.comandhttp://somebody.comarebyOpenIDstandardstwoDIFFERENTpeople,andyetFriendlyIdentifierForDisplaywillbe"somebody.com"forbothofthem.That'swhyyoumustuseClaimedIdentifier,whichincludesthefull,uniquestringoftheuser'sOpenIDwheneveryoulookup"ok,sothisguyisloggingin,butwhere'shisuserrecordanddata?"
AndsinceformsauthenticationinASP.NETmakesHttpContext.Current.User.Namesoeasytogetandmakesecuritydecisionson,youshoulduseClaimedIdentifierwhencreatingtheformsauthticketthereaswell.FriendlyIdentifierForDisplayshouldonlybeusedwhenyou'reemittingHTMLtothebrowsersoyoucansay"Welcome,somebody.com"!Sincethat'seasierontheeyesthan"Welcome,https://somebody.com/".That'sallthefriendlyidentifierisfor.
TheClaimedIdentifierisNOTrotatedateachloginsothattheuserwouldhavetogothroughtheassignmentcycle.It'saconstantforauseracrossalllogins,soit'saperfect(andtheonlysecure)waytolookupauserinyourdatabase.
Ihopethishelps.
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byRickStrahlSeptember18,2009@2:09pm
@Mike-IknowyoucangetemailfromGoogle,butnothingelse.Youcannotgetfullnameoranyotherprofileitemsatleastnotcurrently.
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byRickStrahlSeptember18,2009@7:56pm
@Andrewthanksfortheclarification.I'vechangedthecodeaboveandonthesite(alongwithsomelogictoupdateidscurrentlyinthereifnecessary-lookslikeseveraloftheprovidershavetheclaimedidandfriendlyIdactuallybeingthesamethingthough.
Justforclarificationthough-I'mnotusingtheOpenIdinmyAuthTicketbecausenoteveryoneisusingopenauth.Rathertheuseridisused(thanksyoualsofoundabugthere-Iwasusingthenamenottheid).Istillallowplainusername/passwordloginsandlookingatthesitemostnewsignupsstillusethoseratherthanOpenId.Ihavelessthan10%ofusersusingtheOpenIdcurrently.<shrug>
Thanksforlookingthisover,thefeedbackandofcourseDotNetOpenAuth.It'smadetheactualloginportionveryeasy.
#IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth-RickStrahl'sWebLog
byDotNetShoutoutSeptember21,2009@2:53pm
Thankyouforsubmittingthiscoolstory-TrackbackfromDotNetShoutout
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byJarrettVanceSeptember22,2009@1:14pm
I'vealsodonesomeworkonOpenIDinASP.NETMVC.However,ItookasomewhatoldschoolapproachandcreatedanOpenIdAuthenticationModulethatcouldbepluggedinside-by-sidewiththeFormsAuthenticationModulethatalreadyexists.
SomeworkstillneedstobedonetorequestausernamefrompeopleusingGoogleOpenIdas,fornow,itjustuses"OpenIDuser"astheirnickname.
Youcanseethesourcecode@http://code.google.com/p/atomsite/source/browse/trunk/OpenIdPlugin/OpenIdAuthenticationModule.cs
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byRickStrahlSeptember23,2009@7:03pm
@Jarret-likethatapproachtooandmightactuallybealittlemorereusablearoundtheapplication.Cool,thanksforpostingthelink.
##.think.ininfoDose#43(11thSeptember-22ndSeptember)
by#.think.inOctober02,2009@6:43am
#.think.ininfoDose#43(11thSeptember-22ndSeptember)
#
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byBenjaminRobbinsOctober04,2009@12:09am
I'verecentlyswitchedfromRPXtoDotNetOpenAuthinapersonalproject.I'mveryhappywithDotNetOpenAuth'scapabilities,butifIneededsimpleFacebookorMySpaceintegration,thenI'dgowithRPX.
AsanalternativetousingSessiontostoreyourstate,youcanuseIAuthenticationRequest.AddCallbackArguments(stringkey,stringvalue)duringstage1beforeyousendtheinitialOpenIDrequestandIAuthenticationResponse.GetCallbackArgument(stringkey)afteryoureceivetheauthenticatedresponsefromtheOpenIDproviderduringstage2.ThesemethodsaddandretrieveyourstatefromthequerystringthatispassedaroundduringtheOpenIDmagicdance.Inthisspecificcase,Idon'tthinkusingSessionwouldbethatbigofadeal,butifyouwereinasituationwhereusingSessionwaspainful,thenstoringyourstateinthequerystringbecomesmuchmoreuseful.
#re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byGermánOctober05,2009@3:11am
Hi!
Great,greatsample.InordertoavoidsuchalongmethodIhavesplitteditintwoactionswithanActionMethodSelectorAttributesothere'snoneedofaagreatifcodeblock.
Thanks.Germán.
WhatisOpenId?
OpenIdisasinglesign-onscheme–theideaisthatyoukeepyourloginandprofileinformationinoneplacesothatyoudon’thavetologinateveryWeblocationandcreatenewusercredentialsoneachsite.Theideaofasinglesignonisn’tnewofcourse–lotsofthesethingshavebeenaroundovertheyearswiththemostrememberedprobablybeingMicrosoft’sPassport/WindowsLiveID(notthatanybodylikesWindowsLiveId).OpenIdusesasimilarconcept,butthedifferenceisthattherearemanyprovidersandsitesthatareimplementingOpenIdProviderssothatyoucanusetheirlogoncredentialsthatyouarealreadyusingtologintoothersites.ChancesarethatyoualreadyhaveasignonononeoftheOpenIdprovidersratherthanjustoneproviderthatcontrolsaccess.Youcanessentiallychoosewhereyouloginfrom.SomeofsitesthatareOpenIdprovidersnowareGoogle,Yahoo,MicrosoftWindowsLive,AOL,Facebook,Flickrandmanymanymore.Chancesareyou’resignedupononeoftheseservicesandyoucanusetheirlogonstologintoothersitesthatsupportOpenId.TherushtoOpenIdisfairlyfreshsosomeoftheseworkbetterthanothers.TheonesI’vebeenusingareMyOpenId,GoogleandYahooofwhichMyOpenIdworksbestifyouwanttoshareprofileinformation.TobehonestIhadn’twarmeduptoOpenIduntilrecently.InfactIhadtobeconvincedbyuserrequestsontheCodePaste.netsitetotakealookatintegration.Nowthatit’sthereIthinkitisaprettysweetwaytogoalthoughIthinktheconceptislostontheaverageuseruntilOpenIdbecomesmorewidespread.Italsodidn’thelpthattheoriginalimplementationsandproviderswereprettycrappyandtheprocessonmostsitestosignupactuallywasreallyslowandclunky.ThathaschangedabitwithbetterinterfacesthatarefasterandmanymoresitesareusingOpenIdandmanymoreprovidersexist.
AnotherpainpointisthatsupportingstandardsforretrievingprofileinformationlikeSimpleRegistration(SREG)isnotwidelysupported.Providersarereturninginformationinavarietyofdifferentformatsandmostdon’treturnprofileinformationatallwhetheryouapproveornotwhichisreallylame.Youpresumablyownthedataonanyserviceyou’vegivendatatosowhythefussofsharingitifyouapproveofsharing?Anyway,don’tcountongettingfullcontactinformationfromOpenIdforawhile.Someproviderswillgivesomeinformation,otherswon’tgiveany–soyourapplicationhastobeabletohandlebothscenarios.
OpenIdDevelopment
Fordevelopmentwith.NETthereareafewlibrariesouttherethathandlethecommunicationsdetailsforyou.WhilecommunicationsarehandledusingsimpleURLsforroutingandcallbacksthedataencodedintotheseURLsmustbesecuresothattheycan’tbespoofedandthisprocessisnotexactlytrivial.ForthesiteIchose
Thetoolmakestheactualroutingandhandlingoftheresultsfairlyeasy,buttheintegrationprocessneverthelessisnotcompletelytrivial.ThereasonforthisisthatmostapplicationswillneedtohandlebothloginsaswellasattachinganOpenIdaccounttoanexistinguserprofileofsomesortandthiscangettrickybecauseoftheHTTPGETbasedcallbackinterface.TheactualOpenIdcallsandcallbacksarequitesimple–theactualuserinterfaceandhookuproutinesthoughendeduptakingmealotlongerthanIhadexpected.
WalkingthroughOpenIdAuthentication
AlrightletstakealookandseehowthisworksontheCodePaste.netsite.AsImentionedtherearetwoparts–thebasicloginvalidationwhichisverystraightforwardandsimplesoI’llstartthere.TheloginformdisplaysbothloginsforOpenIdandthetraditionalusernameandpassword:
Figure1–LogginginwithOpenId
IneededtoleavetheoldlogininformplacesoexistinguserscanloginandattachtheirexistingaccountstoOpenId.AlsosomefolksmayjustnotwanttogotheOpenIdroute.Sobotharesupportedonthisform.
OpenIdvalidationstartsbyprovidingaOpenIdUrl.TheUrldependsontheproviderandyoucanpickupthatUrlfromyourprovider’sOpenIdinformationpage–
OncetheLoginbuttonisclicked(orautomaticallyfired)theprocesstakesyoutotheprovider’ssite.ForexampleifyouuseGoogle,you’llseeaGoogleLoginscreenifyouaren’talreadysignedintoGoogleandyouareaskedtologin.Thepagewilltellyouthesourcesitethat’srequestingtheloginandpossiblywhichprofileinformationisrequested.Onceyouloginyouarethenredirectedhenceyoucame–backtothesameurlwhereyoucamefrombydefault.Thiscanbeoverridden,butinanMVCapplicationyou’llmostlikelywillwanttocomebacktothesameURLtoredisplaythelogin(orregistration)datawithsomeindicationthatyouarenowloggedin.
IfyouarealreadyloggedintotheOpenIdprovider(whichisusuallythecase)theloginoperationiscompletelytransparent–thepageisre-routedtotheOpenIdprovidersiteandimmediatelyreturnsbacktoyoursiteataUrlthatisspecifed.Thisusuallyhappensprettyfastthroughabunchofredirects–fromyoursitetotheOpenIdproviderandfrotheOpenIdproviderbacktoyoursite–sootherthansomeextradelaytimeyoudon’tevenseeanythinghappening.Itjustlookslikeyou’regoingdirectlytoyournextpageintheapp.
WhenthepagereturnsafteraloginitincludesencodedauthenticationinformationonthequerystringthatbecomesavailabletotherequestthathandlestheOpenIdlogin.SpecifiallyDotNetOpenAuthcanreadtheauthenticationtokenandanyprofileinformationthatwasreturnedifanywasrequestedandreturned.
Onceloggedinmyprofiledisplaythenreflectstheaccountassociationlikethis:
Figure2–LoggedinwithOpenIdandviewingtheProfile
Theapplicationstorestheuser’sopenidaspartoftheprofileandsoit’seasytodisplaythisinformation.TheOpenIdrelationshipcanbeunlinkedandtheaccountcanthenbeassignedtoanotherOpenIdorausernameandpasswordhastobeprovidedifnoOpenIdisavailable.AsImentionedtheactualOpenIdauthenticationprocessisprettysimplebutsomeoftheissuessurroundingitlikeattachinganddisconnectingaccountsfromOpenIdisalittlemorecomplicated–I’lltalkaboutthatbelow.
IfIgototheprofilepagewhentheaccountisnotassociatedwithanOpenIdthepagehasanoptiontologinandeffectivelyassociatetheaccountwiththenewOpenId:
ThesamepageisalsousedfornewregistrationswhichisusefulinordertoprovidethenameandemailactuallyfromtheOpenIdprovider(assupported–notethatgoogleandyahoodonotprovidethisdatainaformatthatDotnetOpenAuthextractsautomatically).
HowtoimplementOpenIdinanMVCApplication
PleasekeepinmindthatwhatIdescribeisjustonewayyoucandothisinthiscaseusingDotNetOpenAuthsinceitprovidesahostofdifferentwaystoimplementOpenId.TherearehighlevelhelpersandforWebFormsthereisaservercontrolthatyoucanuse.InthisexampleIhandlethisprocessmanuallysoyoucanseetheflowoftherequests.Theprocessisprettysimplealthoughthere’safairbitofcode(mostofitboilerplate)thatisinvolved.I’llstartwiththesigninformbecausethat’ssimpler.Thisrelatestofigure1intheimagesequenceabove.TheOpenIdloginareaofthepageiswrappedintoitsownHTMLformthatkicksofftheprocessing.Thelayoutforthisblocklookslikethis:
<%using(Html.BeginForm(new{Controller="Account",Action="OpenIdLogon"})) {%> <fieldset> <divclass="containercontent"> <divclass="labelheaderblock">LogonusingOpenId:</div> <inputid="openid_identifier"name="openid_identifier"/> <inputid="btnOpenIdLogin"type="submit"value="Login"/> <imgid="imgOpenIdLoginProgress"src="<%=ResolveUrl("~/css/images/loading_small.gif")%>"style="display:none"/> <divid="divOpenIdIcons"> <imgsrc="<%=ResolveUrl("~/images/openid-icon.png")%>"onclick="openIdUrl('openid')"title="myopenid.com"class="hoverbutton"/> <imgsrc="<%=ResolveUrl("~/images/google-icon.png")%>"onclick="openIdUrl('google')"title="Google"class="hoverbutton"/> <imgsrc="<%=ResolveUrl("~/images/yahoo-icon.png")%>"onclick="openIdUrl('yahoo')"title="Yahoo"class="hoverbutton"/> </div> </div> </fieldset> <%}%>
TheimageiconsfireasmallJavaScriptroutinethatcreatestheOpenIdurlsthatareinjectedintothetextbox:
functionopenIdUrl(site) { varvalue=""; varautoClick=false; if(site=="openid"){ value="<YourAccount>.myopenid.com" } elseif(site=="google"){ value="https://www.google.com/accounts/o8/id"; autoClick=true; } elseif(site=="yahoo"){ value="http://yahoo.com/" autoClick=true; } if(value){ varjText=$("#openid_identifier"); jText.val(value) .focus(); if(autoClick) $("#btnOpenIdLogin").trigger("click"); } }
YoucanaddtothiswithotherURLsandiconsasyouseefit.Istuckwiththosethreebecausetheyarethemostcommonthatpeoplewillhaveaccountsonbutthatmaychange.AllothersOpenIdUrlscanbetypedinbyhand(whichisnotsosmooth).
WhentheLoginbuttonisclickedandtherequestissubmitteditfiresintoanOpenIdLogOnactionmethodofthecontroller.ThecontrollerhastoacceptbothPOSTandGEToperations.POSTisfromtheinitialbuttonclick,theGETwilloccurwhentheOpenIdproviderfiresbacktheresponseandprovidesthedataviathequerystring.
AsmentionedI’musingDotNetOpenAuthherewhichisasinglelargeDLLyoucandropintoyourBINfolder.Ithandlesalltheheavyliftingofcreatingtheredirectlinkandcrackingtheresponsequerystringextractingtheauthenticationtoken.
Theprocessinvolvestwosteps:MakingtheoriginalrequesttotheOpenIdprovider(whichredirects)andhandlingtheresponsethatcomesback.Here’sthecode(whichisprettyclosetothesamplecodeprovidedinoneofthedemos):
[AcceptVerbs(HttpVerbs.Post|HttpVerbs.Get),ValidateInput(false)] publicActionResultOpenIdLogOn(stringreturnUrl) { varopenid=newOpenIdRelyingParty(); varresponse=openid.GetResponse();
if(response==null)//Initialoperation { //Step1-SendtherequesttotheOpenIdproviderserver Identifierid; if(Identifier.TryParse(Request.Form["openid_identifier"],outid)) { try { varreq=openid.CreateRequest(Request.Form["openid_identifier"]); returnreq.RedirectingResponse.AsActionResult(); } catch(ProtocolExceptionex) { //displayerrorbyshowingoriginalLogOnview this.ErrorDisplay.ShowError("Unabletoauthenticate:"+ex.Message); returnView("Logon",this.ViewModel); } } else { //displayerrorbyshowingoriginalLogOnview this.ErrorDisplay.ShowError("Invalididentifier"); returnView("LogOn",this.ViewModel); } } else//OpenIdredirectioncallback { //Step2:OpenIDProvidersendingassertionresponse switch(response.Status) { caseAuthenticationStatus.Authenticated: stringidentifier=response.ClaimedIdentifier; //OpenIdlookupfails-Iddoesn'texistforlogin-loginfirst if(busUser.ValidateUserOpenIdAndLoad(identifier)==null) { this.ErrorDisplay.HtmlEncodeMessage=false; this.ErrorDisplay.ShowError(busUser.ErrorMessage+ "Please<ahref='"+WebUtils.ResolveUrl("~/Account/Register")+ "'>register</a>tocreateanewaccountor<ahref='"+ WebUtils.ResolveUrl("~/Account/Register")+ "'>associate</a>anexistingaccountwithyourOpenId"); returnView("LogOn",this.ViewModel); } //CaptureuserinformationforAuthTicket //andissueFormsAuthtoken UserStateuserState=newUserState() { Email=busUser.Entity.Email, Name=busUser.Entity.Name, UserId=busUser.Entity.Id, IsAdmin=busUser.Entity.IsAdmin }; this.IssueAuthTicket(userState,true); if(!string.IsNullOrEmpty(returnUrl)) returnRedirect(returnUrl); returnRedirect("~/new"); caseAuthenticationStatus.Canceled: this.ErrorDisplay.ShowMessage("Canceledatprovider"); returnView("LogOn",this.ViewModel); caseAuthenticationStatus.Failed: this.ErrorDisplay.ShowError(response.Exception.Message); returnView("LogOn",this.ViewModel); } } returnnewEmptyResult(); }
ThecodeusestheOpenIdRelyingPartyclasstohandletheprocessingofthelogin.Thefirststepoccurswhenthebuttonisclickedandthere’snoproviderresponsetoprocess.Thiscode:
varreq=openid.CreateRequest(Request.Form["openid_identifier"]);
returnreq.RedirectingResponse.AsActionResult();
firesoffthetherequest.Theopenid_identifieristheOpenIdUrltheuserenteredintotheformandit’spassedtoDotNetOpenAuthtostartformulatingarequestforauthenticationatthatUrl.
Thecodeabovestartstheredirectionsequencegoingtotheproviderandbacktothecurrentpage-sinceIdidn’tspecifyanotherUrltoreturntobydefaulttherequestreturnstothecurrentpage’surl.
AsmentionedI’musingMVCsoandasyoucanseeDotNetOpenAuthconvenientlyincludesanAsActionResult()methodthatcreatesredirectresponseinaproperMVCstyleresponsewhichisverythoughtfulforconsistency.YoucanalsoretrievetheURLdirectlyandfireitonyourownalthoughIcan’tseewhyyouwould.Ifyou’reusingWebFormsorahandlerthereareothermethodsonRedirectingResponsetofiretheredirectsforyou.
NextthebrowserisredirectedtotheOpenIdproviderwhereyoueitherareaskedtologinorifyoualreadyareloggedinyouareauthenticatedandimmediatelyreturnedtothecurrentpage.WhentherequestreturnsitisnowaGEToperationandwestartbackoutatthetopofthesameControlleraction.
Thistimearound:
varresponse=openid.GetResponse();
willnotbenull–thereturnURLincludesabunchofnewencodedquerystringvalues.GetResponse()looksforthoseandbasedonthatcreatestheresponseinstanceandthecodecanbranchintothegreaterifblock.Therequestwilleitherbeauthenticated,canceledorfailed.Failurecanbeanynumberofthingssuchasinvalidlogininformation,ortimingoutorsomesortoftamperingwiththeURL.Bothcancelandfailureoperationsarehandledbysimplyredisplayingthecurrentviewwithanerrormessage.
Ifauthenticationsucceededwecanretrieveauniqueidentifierthatidentifiestheuser’sproviderchoiceandwe’reguaranteedatthispointthatthisuserwasauthenticatedthroughOpenId.Yay!
AlthoughyounowknowtheuserisauthenticatedASP.NETknowsnothingaboutthisauthenticationyet,sothenextstepistosetaFormsAuthenticationtickettoauthenticatetheusertotheapplication.ThisprocessisnodifferentthandoingamanuallogininanyotherASP.NETapplicationwithFormsAuthentication.
InCodePaste.netIstoreauserrecordintheticketsoIhavesomebasicuserinformationavailabletomyapplicationoneachhitwithouthavingtousesessionstorageorretrievingthisinfooneveryrequestfromthedatabase.Thecodedoesthis:
//CaptureuserinformationforAuthTicket //andissueFormsAuthtoken UserStateuserState=newUserState() { Email=busUser.Entity.Email, Name=busUser.Entity.Name, UserId=busUser.Entity.Id, IsAdmin=busUser.Entity.IsAdmin }; this.IssueAuthTicket(userState,true);
whereIssueAuthTicket()lookslikethisdoingstandardFormsAuthenticationticketassignment:
///<summary> ///IssuesanauthenticationissuefromauserStateinstance ///</summary> ///<paramname="userState"></param> ///<paramname="rememberMe"></param> privatevoidIssueAuthTicket(UserStateuserState,boolrememberMe) { FormsAuthenticationTicketticket=newFormsAuthenticationTicket(1,userState.UserId, DateTime.Now,DateTime.Now.AddDays(10), rememberMe,userState.ToString()); stringticketString=FormsAuthentication.Encrypt(ticket); HttpCookiecookie=newHttpCookie(FormsAuthentication.FormsCookieName,ticketString); if(rememberMe) cookie.Expires=DateTime.Now.AddDays(10); HttpContext.Response.Cookies.Add(cookie); }
Oncetheauthticket’sbeenissuedtheapplicationsimplyredirectstoanotherpageintheapplication(thenewsnippetpageinthiscase).
BTWjustforclarfication,theuserStateobjectcontainslogictoeasilypersisttoandfromstringsothisobjectiseasilyretrievedatthebeginningofarequestandstoredonupdates.InmybasecontrollerIdo:
protectedoverridevoidInitialize(System.Web.Routing.RequestContextrequestContext) { base.Initialize(requestContext); //Grabtheuser'slogininformationfromFormsAuth UserStateuserState=newUserState(); if(this.User.Identity!=null&&this.User.IdentityisFormsIdentity) this.UserState.FromString(((FormsIdentity)this.User.Identity).Ticket.UserData); //havetoexplicitlyaddthissoMastercanseeuntypedvalue this.ViewData["UserState"]=this.UserState; this.ViewData["ErrorDisplay"]=this.ErrorDisplay; }
TheprocesstoauthenticateusingOpenIdisprettystraightforward.Nosurpriseshere,really.AllweneedtohaveissomeconditionallogictorouteforredirectinitiallyandpickuptheresultwhentherequestcomesbackandhandletheauthenticationandcreationofanewFormsAuthenticationticket.Whentherequestreturnsandismarkedasauthenticatedyoucansafelyassumetheuserisvalid.Becauseofthemessageencryptionandtimedsecuritytokensthedataissafeandnothijackable.
Registration–Alittlemorecomplex
Theloginprocessisstraightforwardprimarilybecausetherequestdoesn’tneedtotrackdatathatisalreadyonthepage.Youloginandyoumoveontoanotherpage.Howeverinaloginform(Figure3)theremaybeadditionalpiecesofinformationthatyouneedtotrack.Moreimportantlyyoutypicallyhavesomestateassociatedwiththeregistrationpage–likewhichuser(oranewuser)iscurrentlybeingdisplayedandthatinformationhastobecarriedforwardwhentheOpenIdrequestreturnsfromtheprovider.Sothecodetohandlethisisalittlelonger.TheHTMLlayoutinFigure3isnearlyidenticaltotheHTMLlayoutontheloginformexceptthere’ssomeconditionallogictodisplaytheinputboxorthecheckboxandmessagethatidentifiesthecurrentOpenIdassociation.
<%using(Html.BeginForm(new{Controller="Account",Action="OpenIdRegistrationLogOn"})){%> <fieldset> <divclass="containercontent"style="padding:10px20px;"> <%if(string.IsNullOrEmpty(Model.busUser.Entity.OpenId)){%> <labelfor="openid_identifier"class="labelheaderblock">EnteranOpenIdUrl:</label> <inputid="openid_identifier"size="40"name="openid_identifier"/> <inputid="btnOpenIdLogin"type="submit"value="Login"/> <imgid="imgOpenIdLoginProgress"src="<%=ResolveUrl("~/css/images/loading_small.gif")%>"style="display:none"/> <divid="divOpenIdIcons"> <imgsrc="<%=ResolveUrl("~/images/openid-icon.png")%>"onclick="openIdUrl('openid')"title="myopenid.com"class="hoverbutton"/> <imgsrc="<%=ResolveUrl("~/images/google-icon.png")%>"onclick="openIdUrl('google')"title="Google"class="hoverbutton"/> <imgsrc="<%=ResolveUrl("~/images/yahoo-icon.png")%>"onclick="openIdUrl('yahoo')"title="Yahoo"class="hoverbutton"/> </div> <%}else{%> <labelfor="openid_identifier"class="labelheaderblock">OpenIdAssociation</label> <imgsrc="<%=ResolveUrl("~/css/images/greencheck.gif")%>"/>Thisuseraccountislinkedto<b><%=Html.Encode(Model.busUser.Entity.OpenId)%></b> <inputid="btnOpenIdUnlink"name="btnOpenIdUnlink"type="submit"value="Unlink"/> <%}%> <%=Html.Hidden("Id2",this.Model.busUser.Entity.Id)%> </div> </fieldset> <%}%>
NoticethathereweneedtokeeptrackoftheactiveUserifany.Sinceyoucanattach/detachfromanexistingprofilewedohavetoknowwhichuserwe’redealingwith.Thissoundseasyenoughbutrememberthatyoucan’tuseplainPOSTdataforthisbecausethepagegoesofftheOpenIdproviderandcomesbackwithaGETthatjustholdstheresultvalues.Thismeanstoredisplaythepagetheentirepagehastobere-renderedbasedonthemodel.Todothiswe’llneedtokeeptrackoftheuseridfortherequestsequence.
Thisbaseprocessisverysimilartotheloginprocessandusesthesametwostagesofrequestsendingandreturn.FirstcapturethevaluefromahiddenformvariableandthenstoreitintheSessionobjecttopassbetweenthetworequests.Here’swhattheopenidattachment/detachmentcodelookslike:
[AcceptVerbs(HttpVerbs.Post|HttpVerbs.Get),ValidateInput(false)] publicActionResultOpenIdRegistrationLogOn(FormCollectionformVars) { returnOpenIdRegistrationLogOn(null,true); } privateActionResultOpenIdRegistrationLogOn(IAuthenticationResponseresponse,boolreserved) { this.ViewData["IsNew"]=true;
varopenid=newOpenIdRelyingParty(); if(response==null) response=openid.GetResponse(); if(response==null) { stringuserId=Request.Form["Id2"];//havetotracktheuser’sid //Checkforunlinkoperation if(!string.IsNullOrEmpty(Request.Form["btnOpenIdUnlink"])) { if(busUser.Load(userId)==null) { this.ErrorDisplay.ShowError("Couldn'tfindassociatedUser:"+busUser.ErrorMessage); returnRedirectToAction("Register",new{id=userId}); } busUser.Entity.OpenId=""; busUser.Save(); returnRedirectToAction("Register",new{id=userId}); } Identifierid; stringopenIdIdentifier=Request.Form["openid_identifier"]; if(Identifier.TryParse(openIdIdentifier,outid)) { try { //Weneedtoknowwhichuserweareworkingwith //andsowepasstheidthrusession–(isthereabetterway?) Session["userId"]=userId; varreq=openid.CreateRequest(id); varfields=newClaimsRequest(); fields.Email=DemandLevel.Request; fields.FullName=DemandLevel.Request; req.AddExtension(fields); returnreq.RedirectingResponse.AsActionResult(); } catch(ProtocolExceptionex) { this.ErrorDisplay.ShowError("Unabletoauthenticate:"+ex.Message); this.busUser.NewEntity(); returnView("Register",this.ViewModel); } } else { ViewData["Message"]="Invalididentifier"; returnView("Login"); } } else { //Reestablishtheuserwe’redealingwith stringuserId=Session["userId"]asstring; if(string.IsNullOrEmpty(userId)) ViewData["IsNew"]=true; else ViewData["IsNew"]=false; //Stage3:OpenIDProvidersendingassertionresponse switch(response.Status) { caseAuthenticationStatus.Authenticated: varclaim=response.GetExtension<ClaimsResponse>(); stringemail=null,fullname=null; if(claim!=null) { email=claim.Email; fullname=claim.FullName; } stringidentifier=response.ClaimedIdentifier; varuser=busUser.Load(userId); if(user==null) { user=busUser.ValidateUserOpenIdAndLoad(identifier); if(user==null) user=busUser.NewEntity(); } //associateopenidwiththeuseraccount busUser.Entity.OpenId=identifier; if(!string.IsNullOrEmpty(email)&&string.IsNullOrEmpty(busUser.Entity.Email)) busUser.Entity.Email=email; if(!string.IsNullOrEmpty(fullname)&&string.IsNullOrEmpty(busUser.Entity.Name)) busUser.Entity.Name=fullname; if(string.IsNullOrEmpty(busUser.Entity.Name)) { stringhost=StringUtils.ExtractString(response.FriendlyIdentifierForDisplay,".",".com"); if(!string.IsNullOrEmpty(host)) host="("+host+")"; busUser.Entity.Name="Unknown"+host; this.ErrorDisplay.DisplayErrors.Add("Pleaseenterausername","Name"); } if(!busUser.Validate()) { busUser.Entity.OpenId=""; this.ErrorDisplay.ShowError(busUser.ErrorMessage); returnView("Register",this.ViewModel); } if(!busUser.Save()) { busUser.Entity.OpenId=""; this.ErrorDisplay.ShowError(busUser.ErrorMessage); returnView("Register",this.ViewModel); } UserStateuserState=newUserState() { Name=busUser.Entity.Name, Email=busUser.Entity.Name, UserId=busUser.Entity.Id, IsAdmin=busUser.Entity.IsAdmin }; this.IssueAuthTicket(userState,true); //andreloadthepagewiththesaveddata returnthis.RedirectToAction("Register",new{id=busUser.Entity.Id}); caseAuthenticationStatus.Canceled: this.busUser.NewEntity(true); this.ErrorDisplay.ShowMessage("Canceledatprovider"); returnView("Register",this.ViewModel); caseAuthenticationStatus.Failed: this.busUser.NewEntity(true); this.ErrorDisplay.ShowError(response.Exception.Message); returnView("Register",this.ViewModel); } } returnnewEmptyResult(); }
Therearetwomethodshere–oneasanactualendpointandanotherthatcanbecalledwithanexistingresponsepassedtoit–thisisusefultoautomaticallyforcealoginthatdoesn’texistdirectlyintotheregistrationformviacode.ADotNetAuthresponsecanonlybeparsedonceorelseit’sconsideredinvalidandsopassinginanexistingresponsewasarequirementtohandlethissortof‘manual’routing.ForthesimplecallbackscenarioyoucanjustuseasimpleControllerendpointmethod.
AsinthelastexampletheOpenIdRelyingPartyisusedtohandletheactualrequestsemanticsofsendingtheredirectandreceivingthedata.Thispartoftheprocessreallydoesn’tchange.However,howyoudealwiththedatareceivedandabortscenariosaddsasignificantamountofadditionalcodecomparedtothepreviousloginonlyexample.
Ifthere’snoOpenIdresponse,thecodefirstchecksforunlinkrequests.ForthisitneedstheidtoloadaninstanceofthebusinessentityandupdateitbyremovingtheOpenIdfromit.ThepageisthenredisplayedagainusingtheIDasthekey.
OtherwisetherequestissentofftotheOpenIdprovider.TheproviderthenreturnsandagaintheOpenIdresponsewillnowbeset.OnsuccessthecodetriestoretrievetheClaimsRequestvalues.Claimsrequestissentwiththeinitialrequesttoasktheservertoreturnvaluesfromtheuser’sprofile.ForCodePaste.netI’minterestedinfullnameandemailasoptionalresults.NotethatsomeOpenIdonlyprovidersuseacommonformatcalledSREGtoreturnvalues.myOpenDnsdoesasdoseveralotherofthededicatedservices.However,thisstandardsisonlysketchilysupportedbythebigproviders.GoogleforexamplerequiresthatyouDemanddataandthenwillonlyreturntheemailaddress(whichseemsironicgiventhat’sthemostprivateitemintheprofile).Yahoodoesn’treturnanything–accordingtotheirOpenIdpagetheyonlyshareprofileinformationwithcertainsitestheydeemappropriate.SREGhoweverisgainingmomentumsoit’slikelythatallproviderswillevenutallysupportSREG.
InDotnetOpenAuththerequestfor‘claimdata’ishandledlikethis:
//RequestadditionalProfile
varreq=openid.CreateRequest(Request.Form["openid_identifier"]);informationwithClaimsRequest
varfields=newClaimsRequest();
fields.Email=DemandLevel.Demand;
fields.FullName=DemandLevel.Demand;
req.AddExtension(fields);
YoucanRequestorDemandclaims.IntheoryDemandmeansthatiftheprovideroruserrefusestoprovidetherequestedkeystherequestfails.InrealitythoughproviderslikegoogleignoreRequestcommandsandsoDemandisoftenrequiredtoretrieveinformation.
ToretrievetheclaimsdatausesGetExtension<T>():
//Retrievecustomprofileinformationifavailable
varclaim=response.GetExtension<ClaimsResponse>();
stringemail=null,fullname=null;
if(claim!=null)
{
email=claim.Email;
fullname=claim.FullName;
}
Justrememberthatthere’snoguaranteethesevaluesareset.
Theotherissueinthiscontrollerdealswithstatemanagementoftheuser.Whentheusercomestothepagetohookupwehavestateonthepageforthatparticularuser,butwhenwere-directtotheOpenIdprovidersiteweloosethatstate.SointhiscasetheuseridneedstobetrackedandI’musingaSessionobjectforthis.Ihaven’thadmuchneedforsessioninMVCapplicationsbutthisisonecasewhereIdon’tseeawayaroundthisunfortunately.Beforetherequestissenttheuser’sidisstoredinaSessionobject.Whentheauthenticatedrequestreturnsthesessionidisretrievedandusedtolookuptheuser’sbusinessobjectwhichistheupdatedwiththeopenididentifierandoptionallytheprofileinformationifany.Therestofthebulkycodedealswithafewchecksonthedatareturnedandisolatingwhichdatatoupdate.
OpenIdIntegration–Morecomplicatedthanitlooks
Phew–thislookslikealotofcodeyouhavetowriteandwhileit’saprettygoodchunkthelargestpartofthiscodehastodowiththebusinesslogictosaveandupdatetheuser’sinformationandsettingtheFormsAuthenticationticket.TheactualOpenIdrelatedlogicisrelativelysimpleonceyouunderstandthebasicconceptofhowtheflowofanOpenIdauthenticationworks.AsanyWebbasedcallbackmechanismthecodeisbulkybecauseyouhavetoeffectivelyroutetheresponsesinsideyourcodeandyouendupwithsomecodethatcouplesWebandbusinesslogicwhichisalwaysugly.IrememberfightingsimilarissuesyearsagowithPayPalintegration.ProcesseslikethesearecumbersomebecausetheyaresocloselytiedtotheactualWebrequest–theControllerActioninthiscaseanddon’tprovideforaneasywaytoabstractandwrapintobusinesslogic.That’sthepenaltyyoupayforadistributedsysteminmanysituationsandthisisnodifferent.
TherearealsoothersolutionoutthereforOpenIdintegration.
Ihopethoughthatthisarticleisusefultosomeofyou.IcertainlywishIwouldhavehadsomethingtolookatwhenIstartedlookingatOpenId.There’salotofhighlevelposturingstuffouttherethattalksaboutthebenefitsandgoalsofopenIDbutpreciouslylittlecodethatactuallyshowstheprogressionofstepsincontextthatexplainsOpenIdflowonahighlevelalongwithanimplementation.
Ialsowanttothank
Resources
.NETlibraryforprocessingOpenId,oAuth,InfoCardandvariousotherauthenticationAPIs.Verycomprehensiveandthoughtfulfunctionalityalthoughdocumentationisabitonthesparseside.Excellentsupportandfeedbackfromthedeveloperwho’sreallypassionateaboutthislibraryandcloselymonitorsandlistenstofeedback.
ThisarticleisbasedonthecodeonCodePaste.netandifyoulikeyoucancheckoutthesourcecodefromtheSubversionrepository.
Postedin
FeedbackforthisWeblogEntry
by
You'vebeenkicked(agoodthing)-TrackbackfromDotNetKicks.com
by
Thereisdefinitelyalackofusefulinformationoutthere.ThanksforgettingthisASP.NETwalkthroughtogether.
I'vebeenhopingsomeonewouldputtogetherawellmadevideooftheprocessoutliningtheTECHNICALdetails.Suchasananimatedflowofthedata.
Isn'tthereaspecouttherethatistryingpushusing"username@myhost.com"insteadof"httt://username.myhost.com"?Ibelieveitworksbyfirstsendingarequestouttomyhost.comandgettingaurlpatternback-whichittheninjectstheusernameinto.(IwishIknewwhereIreadaboutthat)
Ithinkusingtheemailpatternwillhelpwithadoption.
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
@William-I'mnotsurethatavideowalkthroughwouldbeallthathelpful-thisisthesortofthingwhereyouneedsomecodetolookatandcopyandpasteandafairbitofit.
Astotheurls-theyareactuallyfairlysimpleformostproviders.TheyareURLsbecausethoseareuniquewhereasemailaddressesareflakeythatwayespeciallywithemailaddresseschanging.ItlookslikemostprovidersaregoingtherouteofusingasimpleURLonly.YahooisjustYahoo.comandtheyfigureouttoroutetotheopenidproviderbasedonthequerystringdatacomingin.IseereallynobenefitofhavinganysortofuseridentificationwiththeURL-ifyou'renotloggedinyouhavetoprovideyourcredentialsanywayanditmakestheurleasiertoworkwithifit'sthesameforallusers.Onceyou'reloggedinwithanOpenIdprovider(whichifyouuseitismostofthetimetheycanpullupyourprofilebeforeyoulogonandauto-fillwhatevernon-privateinformationonthesignonform.
ThesimplerthebetterandthesimplestisthesameURLforeveryoneforeachprovider.
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byRaxitSeptember18,2009@3:38am
ThanksRickforsharingthis.OnethingIamnotclearwithgoogleopenid.IwanttodisplayUserEMailoncetheusersuccessfullygetsauthenticatedandreturntomywebsiteverysimilartowhatyouhavedonebutitreturnstokenizedURLlike
UponsearchingforawhileIgotthisthread
FYI:IamusingcontrolsprovidedwithDotnetOpenAuthi.eOpenIdLogin,OpenIdButton.
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
HiRick,
Firstofall,thanksforyourpost.That'sprobablythemostdetailedandthoughtoutoneI'veseenyet.
Butyouhaveasmall(big)problem.You'reusingIAuthenticationResponse.FriendlyIdentifierForDisplaytomakesecuritydecisions.ItshouldONLYbeusedtodisplaytheidentifiertotheuser(thusthelongname;).YoushouldbeusingIAuthenticationRespones.ClaimedIdentifierastheformsauthticketusernametokeepyoursitesecureagainstidentityspoofingattacks.
by
Thankyouforsubmittingthiscoolstory-TrackbackfromDotNetShoutout
by
Thankyouforsubmittingthiscoolstory-Trackbackfromprogg.ru
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
@Raxit-The'friendlyidentifier'isdeterminedbytheOpenIdproviderandyouhavenocontroloverwhatthatis.Googlereturnsalongandnastystringforthiswhichisfine-youdon'treallyneedtodisplayitanywhere(ijustdidbecauseitwasashowcasemoreorless).Ideallytherealinformationthatyouwanttostore/keep/holdontoisprofileinformationbutasIpointedoutinthearticlethisinformationisofmixedqualityandnotguaranteedtobeprovided.
FriendlyIdentifierinthecodeaboveiswhatDotnetOpenAuthreturns.Ibelieveifsomethingismissingit'lljustreturntheuniqueurlreturnedwhichisrequiredaspartoftheOpenIdconversation.Someothersiteslikemyopenid.netreturnname.myopennet.idasthefriendlyidentifierwhichisnicerforsure.
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
@Andrew-thanksforthefeedback.NotsureIfollowthough.I'mstoringthefriendlyidintheprofileonlyasalookupwhichisONLYcheckedafteraAuthenticationStatus.Authenticatedcallbackthatshouldbeguaranteedtobesecure(otherwiseyourlibrarywouldn'treturnthiscodeanditwouldbepointless,right?:-}).
Theformsauthenticationticketincludes*nothing*atallrelatedtoOpenId.IbasicallyonlyauthenticatethroughOpenIdandonceI'mdoneIissueatotallyunrelatedformsauthenticationticketthathasnodirectconnectiontotheOpenIdidentifieroranythingelseforthatmatter.PlainFormsAuthticket.
SincetheonlywayauthenticationoccursisthroughtheAuthenticationStatus.AuthenticatedcallbackIdon'tseehowthiscouldbeinsecure.ThefriendlyIdismerelyusedasthelookupkeythatmapstheOpenIdaccountandtheuserrecordonmysite.
UsingtheticketretrievedfromOpenIdwouldn'tworkinthisscenariobecausethatticketwillchangewitheachlogin-sonocontinuity.ThewayitisnowiftheFormsAuthticketexpiresortheuserlogsout,nexttimetheyhavere-authenticatewithOpenIdandgothroughthesameassignmentcycleagain.
Ifyouseeaholeherepleasecouldyouclarify,becauseIdon'tseeit?
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
Rick
YouCANgetemailinfofromgoogle...youhavetoenablethisintheweb.configandaddasanextensiononyourrequest:
request.AddExtension(newClaimsRequest
{
Email=DemandLevel.Require,
});
It'sdocumentedonthedotnetopenauthsiteprettywellandIjustimplementedthisforMonoRaileasily.
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
Rick,
YouarecreatingtheauthticketusingtheOpenID"friendlyidentifier":
...newFormsAuthenticationTicket(1,userState.Name...
This,andthefactthatyouusethefriendlyidentifierforlookupiswhat'swrong.YouMUSTuseClaimedIdentifierforusernamelookup,whichinturnmeansyoureallyshoulduseitfortheformsauthticket'susernameaswell.TheFriendlyIdentifierForDisplayisNOTguaranteedtobeuniqueforeachuserastheOpenIDClaimedIdentifieris.Forinstance,
AndsinceformsauthenticationinASP.NETmakesHttpContext.Current.User.Namesoeasytogetandmakesecuritydecisionson,youshoulduseClaimedIdentifierwhencreatingtheformsauthticketthereaswell.FriendlyIdentifierForDisplayshouldonlybeusedwhenyou'reemittingHTMLtothebrowsersoyoucansay"Welcome,somebody.com"!Sincethat'seasierontheeyesthan"Welcome,
TheClaimedIdentifierisNOTrotatedateachloginsothattheuserwouldhavetogothroughtheassignmentcycle.It'saconstantforauseracrossalllogins,soit'saperfect(andtheonlysecure)waytolookupauserinyourdatabase.
Ihopethishelps.
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
@Mike-IknowyoucangetemailfromGoogle,butnothingelse.Youcannotgetfullnameoranyotherprofileitemsatleastnotcurrently.
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
@Andrewthanksfortheclarification.I'vechangedthecodeaboveandonthesite(alongwithsomelogictoupdateidscurrentlyinthereifnecessary-lookslikeseveraloftheprovidershavetheclaimedidandfriendlyIdactuallybeingthesamethingthough.
Justforclarificationthough-I'mnotusingtheOpenIdinmyAuthTicketbecausenoteveryoneisusingopenauth.Rathertheuseridisused(thanksyoualsofoundabugthere-Iwasusingthenamenottheid).Istillallowplainusername/passwordloginsandlookingatthesitemostnewsignupsstillusethoseratherthanOpenId.Ihavelessthan10%ofusersusingtheOpenIdcurrently.<shrug>
Thanksforlookingthisover,thefeedbackandofcourseDotNetOpenAuth.It'smadetheactualloginportionveryeasy.
by
Thankyouforsubmittingthiscoolstory-TrackbackfromDotNetShoutout
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
I'vealsodonesomeworkonOpenIDinASP.NETMVC.However,ItookasomewhatoldschoolapproachandcreatedanOpenIdAuthenticationModulethatcouldbepluggedinside-by-sidewiththeFormsAuthenticationModulethatalreadyexists.
SomeworkstillneedstobedonetorequestausernamefrompeopleusingGoogleOpenIdas,fornow,itjustuses"OpenIDuser"astheirnickname.
Youcanseethesourcecode@
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
by
@Jarret-likethatapproachtooandmightactuallybealittlemorereusablearoundtheapplication.Cool,thanksforpostingthelink.
by
#.think.ininfoDose#43(11thSeptember-22ndSeptember)
re:IntegratingOpenIDinanASP.NETMVCApplicationusingDotNetOpenAuth
byBenjaminRobbinsOctober04,2009@12:09am
I'verecentlyswitchedfromRPXtoDotNetOpenAuthinapersonalproject.I'mveryhappywithDotNetOpenAuth'scapabilities,butifIneededsimpleFacebookorMySpaceintegration,thenI'dgowithRPX.
AsanalternativetousingSessiontostoreyourstate,youcanuseIAuthenticationRequest.AddCallbackArguments(stringkey,stringvalue)duringstage1beforeyousendtheinitialOpenIDrequestandIAuthenticationResponse.GetCallbackArgument(stringkey)afteryoureceivetheauthenticatedresponsefromtheOpenIDproviderduringstage2.ThesemethodsaddandretrieveyourstatefromthequerystringthatispassedaroundduringtheOpenIDmagicdance.Inthisspecificcase,Idon'tthinkusingSessionwouldbethatbigofadeal,butifyouwereinasituationwhereusingSessionwaspainful,thenstoringyourstateinthequerystringbecomesmuchmoreuseful.
byGermánOctober05,2009@3:11am
Hi!
Great,greatsample.InordertoavoidsuchalongmethodIhavesplitteditintwoactionswithanActionMethodSelectorAttributesothere'snoneedofaagreatifcodeblock.
Thanks.Germán.
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Web;
usingSystem.Web.Mvc;
usingDotNetOpenAuth.OpenId.RelyingParty;
usingSystem.Reflection;
namespaceSherezade.Web.Filters
{
publicclassOpenIdAuthenticationCallbackAttribute:ActionMethodSelectorAttribute
{
publicoverrideboolIsValidForRequest(ControllerContextcontrollerContext,System.Reflection.MethodInfomethodInfo)
{
varopenid=newOpenIdRelyingParty();
varresponse=openid.GetResponse();
//WedohaveanopenIdresponse,it'stheprovidercallingback
if(response!=null)
{
//lookforIAuthenticationResponseparameterandpasstheresponseobjecttotheactionaswecan'tcallGetResponse()twicelaterintheactionmethod
varparameterName=methodInfo.GetParameters().Where(pi=>pi.ParameterType.Equals(typeof(IAuthenticationResponse))).Select(pi=>pi.Name).SingleOrDefault();
if(!String.IsNullOrEmpty(parameterName))
{
controllerContext.RouteData.Values.Add(parameterName,response);
}
returntrue;
}
returnfalse;
}
}
}
//handlesopenidformsubmissionfromuser
publicActionResultOpenIdLogin()
{
varopenid=newOpenIdRelyingParty();
Identifierid;
if(Identifier.TryParse(Request.Form["openidIdentifier"],outid))
{
try
{
varreq=openid.CreateRequest(Request.Form["openidIdentifier"]);
varfields=newClaimsRequest();
fields.Email=DemandLevel.Require;
req.AddExtension(fields);
returnreq.RedirectingResponse.AsActionResult();
}
catch(ProtocolExceptionex)
{
returnView("Registro",null);
}
}
else
{
returnView("Registro",null);
}
}
//handlestheprovidercallback
[OpenIdAuthenticationCallback]
publicActionResultOpenIdLogin(IAuthenticationResponseresponse)
{
switch(response.Status)
{
caseAuthenticationStatus.Authenticated:
break;
caseAuthenticationStatus.Canceled:
break;
caseAuthenticationStatus.Failed:
break;
}
returnnewEmptyResult();
}
相关文章推荐
- [转]Sorting, Filtering, and Paging with the Entity Framework in an ASP.NET MVC Application (3 of 10)
- MVC architecture in ASP.Net using C# and Microsoft Data Access Application block
- using Silverlight 4 in an ASP.NET MVC 3 application and accessing data with JSON
- http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application
- Claim-based-security for ASP.NET Web APIs using DotNetOpenAuth
- [转]ASP.NET MVC 3 使用 DotNetOpenAuth 实现SSO
- Sorting, Filtering, and Paging with the Entity Framework in an ASP.NET MVC Application
- ASP.NET MVC 3 使用 DotNetOpenAuth 实现SSO
- Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC Application (9 of 10)
- [原]ASP.NET MVC 3 使用 DotNetOpenAuth 实现SSO
- Preview Word files (docx) in HTML using ASP.NET, OpenXML and LINQ to XML
- How To Implement Forms-Based Authentication in Your ASP.NET Application by Using C# .NET
- How to use an ASP.NET application to query an Indexing Service catalog by using Visual Basic .NET
- Using Membership in ASP.Net MVC 4 (转)
- Rendering a Form in ASP.NET MVC Using HTML Helpers
- Using the Enterprise Library Validation Application Block in ASP.NET - Part I
- Unit Tests for ASP.NET MVC application that uses resources in code behind.
- Preview Word files (docx) in HTML using ASP.NET, OpenXML and LINQ to XML
- Using the Enterprise Library Validation Application Block in ASP.NET - Part II
- Create a more Complex Data Model for an Asp.Net MVC Application