Created
April 9, 2014 08:44
-
-
Save ahelland/10242801 to your computer and use it in GitHub Desktop.
Code for the blog post "Extending Your Azure Active Directory - Part 2" - on http://mobilitydojo.net/2014/04/09/extending-your-azure-active-directory-part-2/
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
using Newtonsoft.Json; | |
using System.Collections.Generic; | |
using System.ComponentModel; | |
namespace DirectoryExtensionsApp.Models | |
{ | |
public class UserContext | |
{ | |
[JsonProperty("odata.metadata")] | |
public string userContext { get; set; } | |
[JsonProperty("value")] | |
public List<UserDetails> value { get; set; } | |
} | |
public class UserDetails | |
{ | |
[JsonProperty("objectId")] | |
public string objectId { get; set; } | |
[DisplayName("Display Name")] | |
public string displayName { get; set; } | |
[DisplayName("Given Name")] | |
public string givenName { get; set; } | |
[DisplayName("Surname")] | |
public string surname { get; set; } | |
[DisplayName("Job Title")] | |
public string jobTitle { get; set; } | |
[DisplayName("Department")] | |
public string department { get; set; } | |
[DisplayName("Mobile")] | |
public string mobile { get; set; } | |
[DisplayName("City")] | |
public string city { get; set; } | |
[DisplayName("Street Address")] | |
public string streetAddress { get; set; } | |
[DisplayName("Country")] | |
public string country { get; set; } | |
[DisplayName("Postal Code")] | |
public string postalCode { get; set; } | |
[DisplayName("Phone Number")] | |
public string telephoneNumber { get; set; } | |
[DisplayName("Email Address")] | |
public string mail { get; set; } | |
[DisplayName("UPN")] | |
public string userPrincipalName { get; set; } | |
[DisplayName("Last DirSync")] | |
public string lastDirSyncTime { get; set; } | |
[DisplayName("YubiKey ID")] | |
public string YubiKeyId { get; set; } | |
} | |
public class AppContext | |
{ | |
[JsonProperty("odata.metadata")] | |
public string metadata { get; set; } | |
public List<AppDetails> value { get; set; } | |
} | |
public class AppDetails | |
{ | |
public string objectId { get; set; } | |
public string appId { get; set; } | |
} | |
public class ExtensionPropertiesContext | |
{ | |
[JsonProperty("odata.metadata")] | |
public string metadata { get; set; } | |
public List<ExtensionProperty> value { get; set; } | |
} | |
public class ExtensionProperty | |
{ | |
public string objectId { get; set; } | |
public string objectType { get; set; } | |
public string name { get; set; } | |
public string dataType { get; set; } | |
[JsonProperty("odata.metadata")] | |
public string odataMetadata { get; set; } | |
[JsonProperty("odata.type")] | |
public string odataType { get; set; } | |
public List<string> targetObjects { get; set; } | |
} | |
} |
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
using System; | |
using System.Configuration; | |
using System.Globalization; | |
using System.Net.Http; | |
using System.Security.Claims; | |
using System.Threading.Tasks; | |
using System.Web; | |
using System.Web.Mvc; | |
using Microsoft.IdentityModel.Clients.ActiveDirectory; | |
using Newtonsoft.Json; | |
using DirectoryExtensionsApp.Models; | |
using DirectoryExtensionsApp.Filters; | |
namespace DirectoryExtensionsApp.Controllers | |
{ | |
public class HomeController : Controller | |
{ | |
private const string TenantIdClaimType = "http://schemas.microsoft.com/identity/claims/tenantid"; | |
private const string LoginUrl = "https://login.windows.net/{0}"; | |
private const string GraphUrl = "https://graph.windows.net"; | |
private const string GraphUserUrl = "https://graph.windows.net/{0}/users/{1}?api-version=2013-04-05"; | |
private const string TenantId = "your_Azure_AD_tenantId"; | |
private static readonly string AppPrincipalId = ConfigurationManager.AppSettings["ida:ClientID"]; | |
private static readonly string AppKey = ConfigurationManager.AppSettings["ida:Password"]; | |
[YubiKeyFilter(TenantId)] | |
public ActionResult Index() | |
{ | |
return View(); | |
} | |
[Authorize] | |
public async Task<ActionResult> UserProfile() | |
{ | |
string tenantId = ClaimsPrincipal.Current.FindFirst(TenantIdClaimType).Value; | |
// Get a token for calling the Windows Azure Active Directory Graph | |
AuthenticationContext authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, LoginUrl, tenantId)); | |
ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey); | |
AuthenticationResult assertionCredential = authContext.AcquireToken(GraphUrl, credential); | |
string authHeader = assertionCredential.CreateAuthorizationHeader(); | |
string requestUrl = String.Format( | |
CultureInfo.InvariantCulture, | |
GraphUserUrl, | |
HttpUtility.UrlEncode(tenantId), | |
HttpUtility.UrlEncode(User.Identity.Name)); | |
HttpClient client = new HttpClient(); | |
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl); | |
request.Headers.TryAddWithoutValidation("Authorization", authHeader); | |
HttpResponseMessage response = await client.SendAsync(request); | |
string responseString = await response.Content.ReadAsStringAsync(); | |
UserProfile profile = JsonConvert.DeserializeObject<UserProfile>(responseString); | |
return View(profile); | |
} | |
} | |
} |
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
@{ | |
ViewBag.Title = "Home Page"; | |
} | |
@if (User.Identity.IsAuthenticated) | |
{ | |
<h3>Hello @User.Identity.Name!</h3> | |
} |
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
using Microsoft.IdentityModel.Clients.ActiveDirectory; | |
using Newtonsoft.Json; | |
using System; | |
using System.Configuration; | |
using System.Globalization; | |
using System.Net.Http; | |
using System.Web; | |
using System.Web.Mvc; | |
using DirectoryExtensionsApp.Models; | |
using System.Net; | |
using System.Security.Claims; | |
namespace DirectoryExtensionsApp.Filters | |
{ | |
public class YubiKeyFilterAttribute : AuthorizeAttribute | |
{ | |
private const string LoginUrl = "https://login.windows.net/{0}"; | |
private const string GraphUrl = "https://graph.windows.net"; | |
private const string GraphApiVersion = "1.21-preview"; | |
private const string GraphUsersUrl = "https://graph.windows.net/{0}/users?api-version=" + GraphApiVersion; | |
private static readonly string AppPrincipalId = ConfigurationManager.AppSettings["ida:ClientID"]; | |
private static readonly string AppKey = ConfigurationManager.AppSettings["ida:Password"]; | |
private static readonly string ExtensionName = ConfigurationManager.AppSettings["ida:ExtensionName"]; | |
public string AuthorizedYubiKey { get; set; } | |
public string tenantId { get; set; } | |
public YubiKeyFilterAttribute(string tenantId) | |
{ | |
this.tenantId = tenantId; | |
} | |
protected override bool AuthorizeCore(HttpContextBase httpContext) | |
{ | |
var clientYubiKeyId = httpContext.Request.QueryString["keyId"]; | |
var isAuthorized = IsAuthorizedYubiKeyId(clientYubiKeyId, tenantId); | |
return isAuthorized; | |
} | |
//The default behavior is a 401 Unauthorized when failing the auth process. | |
//This will trigger a new auth attempt, which is not what we want. | |
//So we override and return 403 instead. | |
protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext) | |
{ | |
filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden); | |
} | |
private static bool IsAuthorizedYubiKeyId(string YubiKeyId, string tenantId) | |
{ | |
//If user has already been authenticated we will OK that without further processing. | |
if (HttpContext.Current.User.Identity.IsAuthenticated) | |
{ | |
return true; | |
} | |
if (!string.IsNullOrEmpty(YubiKeyId)) | |
{ | |
// Get a token for calling the Windows Azure Active Directory Graph | |
AuthenticationContext authContext = new AuthenticationContext(String.Format(CultureInfo.InvariantCulture, LoginUrl, tenantId)); | |
ClientCredential credential = new ClientCredential(AppPrincipalId, AppKey); | |
AuthenticationResult assertionCredential = authContext.AcquireToken(GraphUrl, credential); | |
string authHeader = assertionCredential.CreateAuthorizationHeader(); | |
string requestUrl = String.Format( | |
CultureInfo.InvariantCulture, | |
GraphUsersUrl, | |
HttpUtility.UrlEncode(tenantId)); | |
//Only interested in the users with a matching YubiKeyID | |
requestUrl += "&$filter=" + ExtensionName + " eq " + "'" + YubiKeyId + "'"; | |
HttpClient client = new HttpClient(); | |
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl); | |
request.Headers.TryAddWithoutValidation("Authorization", authHeader); | |
HttpResponseMessage response = client.SendAsync(request).Result; | |
string responseString = response.Content.ReadAsStringAsync().Result; | |
UserContext ctx = JsonConvert.DeserializeObject<UserContext>(responseString); | |
//If we're not getting any results your id was not valid. | |
if (ctx.value.Count == 0) | |
{ | |
return false; | |
} | |
UserDetails user = ctx.value[0]; | |
user.YubiKeyId = YubiKeyId; | |
//Now that we know you're OK we'll create a new identity for you, | |
//and attach it to the current context. | |
ClaimsIdentity ci = new ClaimsIdentity( | |
"Federated", | |
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", | |
"http://schemas.microsoft.com/ws/2008/06/identity/claims/role"); | |
Claim name = new Claim( | |
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", | |
user.userPrincipalName); | |
ci.AddClaim(name); | |
ClaimsPrincipal cp = new ClaimsPrincipal(ci); | |
HttpContext.Current.User = cp; | |
return true; | |
} | |
return false; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment