The new SimpleMembershipProvider
implemented in ASP.NET MVC4 allows easy, built-in support for two popular OpenID providers (Google and Yahoo) and three OAuth providers (Microsoft, Facebook, Twitter).
The providers implemented in DotNetOpenAuth.AspNet.Clients
for use with the SimpleMembershipProvider
all use static URLs for their identity services — that is, all users use the same well-known URL to access the provider. The users' OpenID identifiers are separate from the URL used to access the identity service.
For example, Google's OpenID service URL is https://www.google.com/accounts/o8/id
for all users.
This works with the SimpleMembershipProvider
in MVC4, where the URL of the identity provider needs to be known, constant, and registered at the time your MVC app starts up.
The problem is, other OpenID providers commonly use the user's unique OpenID identifier as the URL to access the identity service.
For example, AOL and WordPress use https://openid.aol.com/{username}
and https://{username}.wordpress.com
, respectively.
If you replace the SimpleMembershipProvider
with your own implementation of an ExtendedMembershipProvider
, then you can roll your own provider implementations, but then it doesn’t work with the MVC4 Account
controller out-of-the-box.
How does one implement a new OpenID Relying Party using the SimpleMembershipProvider
, when the provider uses unique identifiers with the username in the URL?
Best Answer
I’ve developed the following solution that works for me, and I’m sharing in case it will help others, but I would really like to see if there’s a more direct method or “best practice” that I’m missing.
Basically, you need to implement an
OpenIdClient
that is initialized with aProviderIdentifier
that has a URL containing the keyword__username__
.At runtime, the provider name and user name are passed to the
Account
controller, where the provider client is selected by name, and the user name is substituted for the__username__
keyword before the authentication request is sent to the provider.The OpenID Client
The DotNetOpenAuth OpenID provider classes contributed by Microsoft inherit the base class
DotNetOpenAuth.AspNet.Clients.OpenIdClient
, which implements theIAuthenticationClient
interface required for OpenID provider classes. Starting with the source for the Google provider because it has a straight-forward implementation, customize it to make aGenericOpenIdClient
class that works with providers using custom URLs.To create the custom URL at runtime, we’ll accept the OpenID user name as a URI fragment, and replace all instances of
__username__
in the URL with the user name submitted by the user. Providers need to be registered with URLs during application startup, so we can't just register a provider URL at runtime when the user name is known.We'll use OpenID Selector to submit a form to our
Account
controller’sExternalLogin
action with theprovider
form value set to the provider name and username in the formatprovider;{username}
. OpenId Selector has logic built in to substitute all instances of{username}
with input from a textbox presented to the user. On the server side, we’ll split the provider name from the username, look up the provider by name from those registered at application startup, and set theGenericOpenIdClient.UserName
property to the username submitted by the user.When the authentication request is created to send to the OpenID provider, we’ll check the
GenericOpenIdClient.UserName
property, and if set, we’ll recreate the provider URL using the username before sending the request. In order to do so, we need to override theRequestAuthentication()
method to create the authentication request with our custom URL.__username__
is used instead of{username}
here because{
and}
aren't valid characters for a hostname, so creating URLs including them becomes problematic when we need to register them as generic provider identifiers./GenericOpenIdClient.cs
To register the providers built into the new DotNetOpenAuth classes contributed by Microsoft, uncomment the existing Microsoft, Facebook, Twitter, and Google providers, and add a call to register the built-in Yahoo provider. The OpenID providers we’re about to implement don’t need keys, but you’ll need to obtain keys from the OAuth providers (Microsoft, Facebook, and Twitter) if you want to use them. The rest of the providers available in the OpenID Selector package can be added to your liking.
/App_Start/AuthConfig.cs
Last, we need parse the provider form value submitted to the
Account
controller’sExternalLogin
action by OpenID Selector to check for the “;” delimiter indicating a username is present. If so, then we parse out the provider name and username./Controllers/AccountController.cs
The UI
The UI implementation is made much easier with the open-source OpenID Selector. Download OpenID Selector and customize it for use with the
OAuthWebSecurity
classes.openid
folder in your web app at:/Content/openid
css
,images
,images.large
, andimages.small
folders from theopenid-selector
download to the/Content/openid
folder, then include the files in your project.js
folder, copyopenid-jquery.js
andopenid-en.js
to your web app's/Scripts
folder, then include the files your project.openid-en.js
file and customize it so the provider URLs are the provider names you will add in yourAuthConfig.cs
file. For providers with custom URLs, use the formatProvider;{username}
:/Scripts/openid-en.js
OpenID Selector doesn’t come with images for Microsoft or Twitter, so download your favorite Microsoft and Twitter (blue on white) logos, convert them to GIFs at 100x60 pixels, then drop them in the
/Content/openid/images.large
folder. Read the instructions in the OpenID SelectorREADME.txt
file if you want to use a single sprite image instead if separate images. Setopenid.no_sprite = false;
inopenid-en.js
if you use the sprite.Register the JS and CSS files as a new bundle. Open
/App_Start/BundleConfig.cs
and add the following script and style bundles in theRegisterBundles()
method./App_Start/BundleConfig.cs
I prefer the OpenID Selector's "shadow" style, so I elected use just the
openid-shadow.css
CSS file and customized the following classes to work in the MVC4 Login template./Content/css/openid/openid-shadow.css
To create a generic place to add CSS scripts to the page's
<head>
tag, add ahead
section at the bottom of the<head>
tag./Views/Shared/_Layout.cshtml
Then, in the
/Views/Account/Login.cshtml
file, customize theLogin
view by adding the OpenID bundles we registered previously to the appropriate sections at the bottom of the page./Views/Account/Login.cshtml
The last element of the UI involves replacing the default
ExternalLogin
form with the OpenID Selector form./Views/Account/_ExternalLoginsListPartial.cshtml