Created
April 9, 2011 22:11
-
-
Save woloski/911826 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@if(Request.IsAuthenticated) { | |
<text>Welcome <b>@Context.User.Identity.Name</b>! | |
[ @Html.ActionLink("Log Off", "LogOff", "Account") ]</text> | |
} | |
else { | |
<a href="#" id="logon">Log On</a> | |
<div id="popup_logon"> | |
</div> | |
<style type="text/css"> | |
#popup_logon ul | |
{ | |
list-style: none; | |
} | |
#popup_logon ul li | |
{ | |
margin: 10px; | |
padding: 10px | |
} | |
</style> | |
<script type="text/javascript"> | |
$("#logon").click(function() { | |
$("#popup_logon").html("<p>Loading...</p>"); | |
$("#popup_logon").dialog({ modal: true, draggable: false, resizable: false, title: 'Select your preferred login method' }); | |
$.ajax({ | |
url : '@Html.Raw(Url.Action("IdentityProviders", "Account", new { serviceNamespace = "YourServiceNamespace", appId = "https://yourwebapp/" }))', | |
success : function(data){ | |
dialogHtml = '<ul>'; | |
for (i=0; i<data.length; i++) | |
{ | |
dialogHtml += '<li>'; | |
if (data[i].ImageUrl == '') | |
{ | |
dialogHtml += '<a href="' + data[i].LoginUrl + '">' + data[i].Name + '</a>'; | |
} else | |
{ | |
dialogHtml += '<a href="' + data[i].LoginUrl + '"><img style="border: 0px; width: 100px" src="' + data[i].ImageUrl + '" alt="' + data[i].Name + '" /></a>'; | |
} | |
dialogHtml += '</li>'; | |
} | |
dialogHtml += '</ul>'; | |
$("#popup_logon").html(dialogHtml); | |
} | |
}) | |
}); | |
</script> | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace YourWebApp.Controllers | |
{ | |
using System; | |
using System.Text; | |
using System.Web.Mvc; | |
using System.Web.Routing; | |
using System.Web.Security; | |
using Microsoft.IdentityModel.Protocols.WSFederation; | |
using Microsoft.IdentityModel.Web; | |
using System.Net; | |
[HandleError] | |
[ValidateInput(false)] | |
[OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")] | |
public class AccountController : Controller | |
{ | |
private const string IdentityProviderJsonEndpoint = "https://{0}.accesscontrol.appfabriclabs.com/v2/metadata/IdentityProviders.js?protocol=wsfederation&realm={1}&reply_to=&context=&request_id=&version=1.0"; | |
#region Action Methods | |
/// <summary> | |
/// This method signs in the user using Federated authentication. | |
/// </summary> | |
/// <remarks> | |
/// If the LogOn link was explicitly clicked, the return URL will be empty. | |
/// If the LogOn was triggered due to authorization requirements on a page, | |
/// a return URL will be present. It is expected that after authentication, | |
/// the user will be redirected back to this return URL. | |
/// </remarks> | |
/// <param name="returnUrl">URL the user was trying to access before being authenticated. This parameter is optional.</param> | |
/// <returns> | |
/// Redirect to return URL if the request is authenticated; | |
/// otherwise Federated sign in Redirect to the STS. | |
/// </returns> | |
public ActionResult LogOn(string returnUrl) | |
{ | |
return this.LogOnCommon(returnUrl); | |
} | |
/// <summary> | |
/// This method signs in the user using Federated authentication. | |
/// </summary> | |
/// <remarks> | |
/// If the POST is an unauthenticated POST, the request will be | |
/// redirected to the STS for Federated authentication. | |
/// If the protocol form POST is received with the token from | |
/// the STS, since the WSFederationAuthenticationModule is configured in the pipeline, | |
/// it will have authenticated the posted token by the time this method is invoked. | |
/// Therefore, this method turns off input validation. | |
/// </remarks> | |
/// <returns> | |
/// Redirect to return URL if the request is authenticated; | |
/// otherwise Federated sign in Redirect to the STS. | |
/// </returns> | |
[ValidateInput(false)] | |
[AcceptVerbs(HttpVerbs.Post)] | |
public ActionResult LogOn() | |
{ | |
return this.LogOnCommon(null); | |
} | |
/// <summary> | |
/// This method signs out the user from the current application and | |
/// issues a Federated sign out Redirect to the STS. | |
/// </summary> | |
/// <remarks> | |
/// When a protocol sign out cleanup message is received from the STS, | |
/// since the WSFederationAuthenticationModule is configured in the pipeline, | |
/// it will process the sign out cleanup message and return the appropriate | |
/// response back to the STS. Therefore, this LogOff() method does not | |
/// need to handle sign out/sign out cleanup messages that are received. | |
/// </remarks> | |
/// <returns>Federated sign out Redirect to the STS</returns> | |
public ActionResult LogOff() | |
{ | |
WSFederationAuthenticationModule fam = FederatedAuthentication.WSFederationAuthenticationModule; | |
// SignOut from both Authentications | |
try | |
{ | |
FormsAuthentication.SignOut(); | |
} | |
finally | |
{ | |
fam.SignOut(true); | |
} | |
// Initiate a Federated sign out request to the STS. | |
SignOutRequestMessage signOutRequest = new SignOutRequestMessage(new Uri(fam.Issuer), fam.Realm); | |
return Redirect(signOutRequest.WriteQueryString()); | |
} | |
public ActionResult IdentityProviders(string serviceNamespace, string appId) | |
{ | |
string idpsJsonEndpoint = string.Format(IdentityProviderJsonEndpoint, serviceNamespace, appId); | |
var client = new WebClient(); | |
var data = client.DownloadData(idpsJsonEndpoint); | |
return Content(Encoding.UTF8.GetString(data), "application/json"); | |
} | |
//public ActionResult FetchMetadata(string url) | |
//{ | |
// var client = new WebClient(); | |
// var data = client.DownloadData("https://login.southworks.net/FederationMetadata/2007-06/FederationMetadata.xml"); | |
// var request = WebRequest.Create(url); | |
// var response = request.GetResponse() as HttpWebResponse; | |
// return Content((int)response.StatusCode, "text/plain"); | |
//} | |
#endregion | |
#region LogOn Helpers | |
/// <summary> | |
/// This method extracts the WS-Federation passive context from the current HTTP request, | |
/// if it is a valid protocol message. | |
/// </summary> | |
/// <returns>Context string if it exists; otherwise String.Empty</returns> | |
private string GetContextFromRequest() | |
{ | |
Uri requestBaseUrl = WSFederationMessage.GetBaseUrl(Request.Url); | |
WSFederationMessage message = WSFederationMessage.CreateFromNameValueCollection(requestBaseUrl, Request.Form); | |
return message != null ? message.Context : String.Empty; | |
} | |
private string GetWReply() | |
{ | |
Uri reqUrl = Request.Url; | |
var wreply = new StringBuilder(); | |
wreply.Append(reqUrl.Scheme); // e.g. "https" | |
wreply.Append("://"); | |
wreply.Append(Request.Headers["Host"] ?? reqUrl.Authority); | |
wreply.Append(Request.ApplicationPath); | |
if (!Request.ApplicationPath.EndsWith("/", StringComparison.OrdinalIgnoreCase)) | |
{ | |
wreply.Append("/"); | |
} | |
wreply.Append("Account/LogOn"); | |
return wreply.ToString(); | |
} | |
/// <summary> | |
/// This method constructs a Federated sign in request to the STS | |
/// based on the current configuration of the WSFederationAuthenticationModule. | |
/// </summary> | |
/// <param name="returnUrl">URL the user was trying to access before being authenticated. This parameter is optional.</param> | |
/// <returns>A WS-Federation passive protocol request URL serialized to string</returns> | |
private string GetFederatedSignInRedirectUrl(string returnUrl) | |
{ | |
// Create a sign in request based on the configured parameters. | |
WSFederationAuthenticationModule fam = FederatedAuthentication.WSFederationAuthenticationModule; | |
var signInRequest = new SignInRequestMessage(new Uri(fam.Issuer), fam.Realm) | |
{ | |
AuthenticationType = fam.AuthenticationType, | |
Context = this.GetReturnUrl(new RequestContext(HttpContext, RouteData)), | |
Freshness = fam.Freshness, | |
Reply = this.GetWReply(), | |
HomeRealm = fam.HomeRealm | |
}; | |
return signInRequest.WriteQueryString(); | |
} | |
/// <summary> | |
/// This LogOn method accepts both GET and POST requests. | |
/// | |
/// Case 1: Unauthenticated GET & POST - result in Federated sign in Redirect to the STS. | |
/// | |
/// Case 2: Authenticated GET - results in Redirect to the return URL. | |
/// | |
/// Case 3: Authenticated POST - When the protocol form POST is received with the token from | |
/// the STS, since the WSFederationAuthenticationModule is configured in the pipeline, | |
/// it will have authenticated the posted token by the time this method is invoked. | |
/// Therefore, this method will treat an authenticated POST request the same as an | |
/// authenticated GET, and issues a Redirect to the return URL. | |
/// | |
/// </summary> | |
/// <param name="returnUrl">URL the user was trying to access before being authenticated. This parameter is optional.</param> | |
/// <returns> | |
/// Redirect to return URL if the request is authenticated; | |
/// otherwise Federated sign in Redirect to the STS. | |
/// </returns> | |
private ActionResult LogOnCommon(string returnUrl) | |
{ | |
// If the request is unauthenticated, Redirect to the STS with a protocol request. | |
if (!Request.IsAuthenticated) | |
{ | |
string federatedSignInRedirectUrl = this.GetFederatedSignInRedirectUrl(returnUrl); | |
return Redirect(federatedSignInRedirectUrl); | |
} | |
// Request is already authenticated. | |
// Redirect to the URL the user was trying to access before being authenticated. | |
string effectiveReturnUrl = returnUrl; | |
// If no return URL was specified, try to get it from the Request context. | |
if (String.IsNullOrEmpty(effectiveReturnUrl)) | |
{ | |
effectiveReturnUrl = this.GetContextFromRequest(); | |
} | |
// If there is a return URL, Redirect to it. Otherwise, Redirect to Home. | |
if (!String.IsNullOrEmpty(effectiveReturnUrl)) | |
{ | |
return Redirect(effectiveReturnUrl); | |
} | |
else | |
{ | |
return RedirectToAction("Index", "Home"); | |
} | |
} | |
private string GetReturnUrl(RequestContext context) | |
{ | |
var request = context.HttpContext.Request; | |
var reqUrl = request.Url; | |
var returnUrl = new StringBuilder(); | |
returnUrl.Append(reqUrl.Scheme); | |
returnUrl.Append("://"); | |
returnUrl.Append(request.Headers["Host"] ?? reqUrl.Authority); | |
returnUrl.Append(request.RawUrl); | |
if (!request.ApplicationPath.EndsWith("/", StringComparison.OrdinalIgnoreCase)) | |
{ | |
returnUrl.Append("/"); | |
} | |
return returnUrl.ToString(); | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment