Using App Model V2 to get access to calendar - azure

We have a webapplication on ASP.NET in Azure and we want to get access to the current user to his calendar to show the events for today and the number of unread emails. We have application that used graph.microsoft.com with default "Work or School Account"authentication that is created with Visual Studio, but this does not work with App Model V2.
How can build an applicaiton that is able to authenticate using App Model V2 and get access to graph.microsoft.com?

You need to use Microsoft.IdentityModel.Clients.ActiveDirectory;
A good samples is given in
https://azure.microsoft.com/en-us/documentation/articles/active-directory-appmodel-v2-overview/
The step that you need to take for a App Model V2 application are:
Register your application using the application registration portal on https://apps.dev.microsoft.com. Remember the clientID and clientsecret that is registered for you.
Create an asp.net in VS2015 without authentication (anonymous)
Add the Nuget Package Microsoft.IdentityModel.Clients.ActiveDirectory
Add using Microsoft.IdentityModel.Clients.ActiveDirectory to the controller
You need to add scope to your code as private member
private static string[] scopes = {
"https://graph.microsoft.com/calendars.readwrite" };
Add add the following settings to web.config
<add key="ida:ClientID" value="..." />
<add key="ida:ClientSecret" value="..." />
You have to create 2 extra methods. One for the signin and one for the authorize:
Signin:
public async Task<ActionResult> SignIn()
{
string authority = "https://login.microsoftonline.com/common/v2.0";
string clientId = System.Configuration.ConfigurationManager.AppSettings["ida:ClientID"];
AuthenticationContext authContext = new AuthenticationContext(authority);
// The url in our app that Azure should redirect to after successful signin
Uri redirectUri = new Uri(Url.Action("Authorize", "Home", null, Request.Url.Scheme));
// Generate the parameterized URL for Azure signin
Uri authUri = await authContext.GetAuthorizationRequestUrlAsync(scopes, additionalScopes, clientId,
redirectUri, UserIdentifier.AnyUser, null);
// Redirect the browser to the Azure signin page
return Redirect(authUri.ToString());
}
Authorize:
public async Task<ActionResult> Authorize()
{
// Get the 'code' parameter from the Azure redirect
string authCode = Request.Params["code"];
string authority = "https://login.microsoftonline.com/common/v2.0";
string clientId = System.Configuration.ConfigurationManager.AppSettings["ida:ClientID"];
string clientSecret = System.Configuration.ConfigurationManager.AppSettings["ida:ClientSecret"];
AuthenticationContext authContext = new AuthenticationContext(authority);
// The same url we specified in the auth code request
Uri redirectUri = new Uri(Url.Action("Authorize", "Home", null, Request.Url.Scheme));
// Use client ID and secret to establish app identity
ClientCredential credential = new ClientCredential(clientId, clientSecret);
try
{
// Get the token
var authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
authCode, redirectUri, credential, scopes);
// Save the token in the session
Session["access_token"] = authResult.Token;
return Redirect(Url.Action("Tasks", "Home", null, Request.Url.Scheme));
}
catch (AdalException ex)
{
return Content(string.Format("ERROR retrieving token: {0}", ex.Message));
}
}
The accestoken is in a session state.
Now you can call graph.microsoft.com with the correct accesstoken and get the data:
private async Task<List<DisplayEvent>> GetEvents()
{
List<DisplayEvent> tasks = new List<DisplayEvent>();
HttpClient httpClient = new HttpClient();
var accessToken = (string)Session["access_token"];
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await httpClient.GetAsync("https://graph.microsoft.com/beta/users/me/events");
if (response.IsSuccessStatusCode)
{
string s = await response.Content.ReadAsStringAsync();
JavaScriptSerializer serializer = new JavaScriptSerializer();
EventModels eventList = serializer.Deserialize<EventModels>(s);
foreach (EventModel v in eventList.value)
{
//Fill tasks will events
}
}
return tasks;
}

Related

HowTo: ADB2C User Groups in Token

I am running a ADB2C tenant and would like to know how to configure and retrieve user groups within a auth token in the ADB2C auth flow.
I am able to configure and receive the custom attribute in my token but I am unable to configure groups claim that can potentially list user membership to a certain group(s).
expected is a groups:{}
In the B2C, there is no group claim that will returns in the token, so you cannot follow the same way in the Azure AD.
You could try to let the application manually retrieve these claims the group claims and inject them into the token.
For this way, you could refer to the method in Azure AD:
var authority = $"https://login.microsoftonline.com/{Tenant}";
var graphCca = new ConfidentialClientApplication(GraphClientId, authority, GraphRedirectUri, new ClientCredential(GraphClientSecret), userTokenCache, null);
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
try
{
AuthenticationResult authenticationResult = await graphCca.AcquireTokenForClientAsync(scopes);
string token = authenticationResult.AccessToken;
using (var client = new HttpClient())
{
string requestUrl = $"https://graph.microsoft.com/v1.0/users/{signedInUserID}/memberOf?$select=displayName";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
HttpResponseMessage response = await client.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(responseString);
foreach (var group in json["value"])
notification.AuthenticationTicket.Identity.AddClaim(new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Role, group["displayName"].ToString(), System.Security.Claims.ClaimValueTypes.String, "Graph"));
//TODO: Handle paging.
// https://developer.microsoft.com/en-us/graph/docs/concepts/paging
// If the user is a member of more than 100 groups,
// you'll need to retrieve the next page of results.
}
} catch (Exception ex)
{
//TODO: Handle
throw;
}

How do you request the Azure Resource Management endpoint scope correctly with MSAL?

So I read that if you add .default at the end of a resource URI in a scope it would return us a a proper v1 token. When setting the protectedResourceMap for MSAL what exactly should the scope be? 'https://management.azure.com/.default' doesn't seem to work. Nor does 'https://management.azure.com/user_impersonation'.
What is the proper way to setup the scope so when requesting consent to our app they approve the Azure management APIs?
Use two slashes like this:
https://management.core.windows.net//.default
"This is because the ARM API expects a slash in its audience claim (aud), and then there is a slash to separate the API name from the scope."
Source:
https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Adal-to-Msal
Here's a complete example:
void Main()
{
var tenantId = "<tenantId>";
var clientId = "<clientId>";
var clientSecret = "<clientSecret>";
var credentials = GetCredentials(tenantId, clientId, clientSecret);
Console.WriteLine(credentials);
}
public static async Task<AuthenticationResult> GetCredentials(string tenantId, string clientId, string clientSecret)
{
string authority = $"https://login.microsoftonline.com/{tenantId}/";
IConfidentialClientApplication app;
app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authority))
.Build();
IEnumerable<string> scopes = new List<string>() { "https://management.core.windows.net//.default" };
var result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
return result;
}
Screenshot of the AuthenticationResult object in LINQPad:
Sample code from here:
https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-netcore-daemon

Azure AAD - Application Proxy to On-Premise Service

I've been trying to call an on-premise Api from another Api in Azure, using Application Proxy. I've followed several examples like the one listed below, with similar results. I can successfully acquire an access token but when I make an http call to on-prem Api via app proxy, I always get redirected to the sign-in page even though I've added an authorization header with bearer token. Anybody have thoughts on what I may be missing?
https://blogs.technet.microsoft.com/applicationproxyblog/2018/06/27/securing-api-access-beyond-intranet-with-azure-ad-application-proxy/
This is the code I'm running that was copied from the referenced example. The one thing I'm not 100% on is the todoListResourceId & todoListBaseAddress. I have them as the same value which is the "Homepage URL" of the configured Application Proxy app.
AuthenticationContext authContext;
private static string aadInstance = "https://login.microsoftonline.com/{0}";
private static string tenant = "xxx.onmicrosoft.com";
private static string clientId = "my app id";
Uri redirectUri = new Uri("http://replyurl");
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
private static string todoListResourceId = "https://xxx.msappproxy.net";
private static string todoListBaseAddress = "https://xxx.msappproxy.net";
private async void GetTodoList()
{
AuthenticationResult result = null;
HttpClient httpClient = new HttpClient();
authContext = new AuthenticationContext(authority);
result = await authContext.AcquireTokenAsync(todoListResourceId, clientId, redirectUri, new PlatformParameters(PromptBehavior.Auto));
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
// Call the To Do list service.
HttpResponseMessage response = await httpClient.GetAsync(todoListBaseAddress + "/api/values/4");
//MessageBox.Show(response.RequestMessage.ToString());
string s = await response.Content.ReadAsStringAsync();
MessageBox.Show(s);
}

Extend MSAL to support multiple Web APIs

Hi I started with the sample here
https://github.com/Azure-Samples/active-directory-b2c-dotnet-webapp-and-webapi
And manqaged to get the TaskWebApp talking to the TaskService.
I now want to extend the solution to have a 3rd service
Im not sure if the problem is within the ConfidentialClientApplication as when it fails to get my tokens i get no exception.
I think the issue is the COnfigureApp method in my TaskWebApp is only expecting to manage tokens from the single TokenService.
Here is my web App Starttup.Auth ConfigureAuth method, this is unchanged from the sample app
public void ConfigureAuth(IAppBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
// Generate the metadata address using the tenant and policy information
MetadataAddress = String.Format(AadInstance, Tenant, DefaultPolicy),
// These are standard OpenID Connect parameters, with values pulled from web.config
ClientId = ClientId,
RedirectUri = RedirectUri,
PostLogoutRedirectUri = RedirectUri,
// Specify the callbacks for each type of notifications
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = OnRedirectToIdentityProvider,
AuthorizationCodeReceived = OnAuthorizationCodeReceived,
AuthenticationFailed = OnAuthenticationFailed,
},
// Specify the claims to validate
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name"
},
// Specify the scope by appending all of the scopes requested into one string (seperated by a blank space)
Scope = $"{OpenIdConnectScopes.OpenId} {ReadTasksScope} {WriteTasksScope}"
}
);
}
Here is my OnAuthorizationCodeReceived method, again unchanged
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification notification)
{
// Extract the code from the response notification
var code = notification.Code;
var userObjectId = notification.AuthenticationTicket.Identity.FindFirst(ObjectIdElement).Value;
var authority = String.Format(
AadInstance,
Tenant,
DefaultPolicy);
var httpContext = notification.OwinContext.Environment["System.Web.HttpContextBase"] as HttpContextBase;
// Exchange the code for a token. Make sure to specify the necessary scopes
ClientCredential cred = new ClientCredential(ClientSecret);
ConfidentialClientApplication app = new ConfidentialClientApplication(
authority,
Startup.ClientId,
RedirectUri,
cred,
new NaiveSessionCache(userObjectId, httpContext));
var authResult = await app.AcquireTokenByAuthorizationCodeAsync(new string[]
{
ReadTasksScope,
WriteTasksScope
},
code,
DefaultPolicy);
}
The problem seems to be that my Readand write scope are referencing the TaskService directly and when i try to ask for appropriate scopes from other apps it complains.
in my controller
private async Task<String> acquireToken(String[] scope)
{
try
{
string userObjectID = ClaimsPrincipal.Current.FindFirst(Startup.ObjectIdElement).Value;
string authority = String.Format(Startup.AadInstance, Startup.Tenant, Startup.DefaultPolicy);
ClientCredential credential = new ClientCredential(Startup.ClientSecret);
// Retrieve the token using the provided scopes
ConfidentialClientApplication app = new ConfidentialClientApplication(authority, Startup.ClientId,
Startup.RedirectUri, credential,
new NaiveSessionCache(userObjectID, this.HttpContext));
AuthenticationResult result = await app.AcquireTokenSilentAsync(scope);
return result.Token;
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
throw e;
}
This method is used but My AuthorisationResult fails with the message couldnt get token silently with no inner exception.
So far I have tried removing the NaiveSessionCache as its optional and it still fails
To get past this I have registered a 4th service and then made my webapis use that single client id and secret to control access to their resources but this feels like it is not the correct way forward.
Any help would be great thanks

Azure AD PostAuthentication add claims

I am using Azure AD to authenticate the users. I want to add few user claims specific to my application. Should I do it in Application_PostAuthenticateRequest` in global.asax ?. Is there a way I can cache my claims too ?
If you are using the ASP.NET OWIN middleware, there are specific notifications you can use for that purpose. Claims added in that way will end up in your session cookie, so that you won't have to repeat the claims augmentation logic in subsequent calls. See http://www.cloudidentity.com/blog/2015/08/26/augmenting-the-set-of-incoming-claims-with-the-openid-connect-and-oauth2-middleware-in-katana-3-x/ for details.
BTW you can add your custom cliams but you cannot override the existing claims added by the Azure AD (what i have seen so far might be i am wrong). what you can do is to add the new cliams like this
AuthorizationCodeReceived = context =>
{
List<System.Security.Claims.Claim> allcustomClaims = new List<System.Security.Claims.Claim>();
allcustomClaims.Add(new System.Security.Claims.Claim("customClaim", "YourDefindedValue"));
context.AuthenticationTicket.Identity.AddClaims(allcustomClaims);
return Task.FromResult(0);
}`
and then you can get the claim anywhere in controller like
#{
var claimsIdentity = User.Identity as System.Security.Claims.ClaimsIdentity;
if (claimsIdentity != null)
{
var c = claimsIdentity.FindFirst("customClaim").Value;
}
}
You can augment the claims programmatically like this:
public async Task<ActionResult> AuthenticateAsync()
{
ClaimsPrincipal incomingPrincipal = System.Threading.Thread.CurrentPrincipal as ClaimsPrincipal;
if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated == true)
{
ClaimsIdentity claimsIdentity = incomingPrincipal.Identity as ClaimsIdentity;
if (!claimsIdentity.HasClaim(ClaimTypes.Role, "Admin"))
{
claimsIdentity.AddClaim(new Claim(ClaimTypes.Role, "Admin", ClaimValueTypes.String, "AADGuide"));
var ctx = Request.GetOwinContext();
var authenticationManager = ctx.Authentication;
AuthenticateResult authResult = await authenticationManager.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationType);
authenticationManager.SignIn(authResult.Properties,claimsIdentity);
}
}
return RedirectToAction("Index", "Start");
}
This solution relies on AuthenticationAsync method of AuthenticationManager to retrieve the original AuthenticationProperties. After retrieving the properties, call the SignIn method to persist the new ClaimsIdentity in the auth cookie.
If you're making use of:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
...
This is how I managed to add additional custom claims using new OAuthBearerAuthenticationProvider:
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
// The id of the client application that must be registered in Azure AD.
TokenValidationParameters = new TokenValidationParameters { ValidAudience = clientId },
// Our Azure AD tenant (e.g.: contoso.onmicrosoft.com).
Tenant = tenant,
Provider = new OAuthBearerAuthenticationProvider
{
// In this handler we can perform additional coding tasks...
OnValidateIdentity = async context =>
{
try
{
// Retrieve user JWT token from request.
var authorizationHeader = context.Request.Headers["Authorization"].First();
var userJwtToken = authorizationHeader.Substring("Bearer ".Length).Trim();
// Get current user identity from authentication ticket.
var authenticationTicket = context.Ticket;
var identity = authenticationTicket.Identity;
// Credential representing the current user. We need this to request a token
// that allows our application access to the Azure Graph API.
var userUpnClaim = identity.FindFirst(ClaimTypes.Upn);
var userName = userUpnClaim == null
? identity.FindFirst(ClaimTypes.Email).Value
: userUpnClaim.Value;
var userAssertion = new UserAssertion(
userJwtToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userName);
identity.AddClaim(new Claim(identity.RoleClaimType, "myRole"));
}
catch (Exception e)
{
throw;
}
}
}
});
For a full sample, check this blog post.

Resources