Skip to content

Instantly share code, notes, and snippets.

@ahelland
Created April 9, 2014 08:44
Show Gist options
  • Save ahelland/10242801 to your computer and use it in GitHub Desktop.
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/
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; }
}
}
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);
}
}
}
@{
ViewBag.Title = "Home Page";
}
@if (User.Identity.IsAuthenticated)
{
<h3>Hello @User.Identity.Name!</h3>
}
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