Hello I am trying to get user info like name and address in an Xamarin app using Azure AD B2C for authentication.
So far I've gotten the authentication working fine
public async Task<MobileServiceUser> LoginAsync(MobileServiceClient client, MobileServiceAuthenticationProvider provider)
{
try
{
//login and save user status
var user = await client.LoginAsync(Forms.Context, provider);
Settings.AuthToken = user?.MobileServiceAuthenticationToken ?? string.Empty;
Settings.UserId = user?.UserId ?? string.Empty;
return user;
}
catch (Exception e)
{
}
return null;
}
However I would like to know how to get the user's name and birthday. I haven't been able to find a clear course of action for that.
You do not explicitly get this information using the MobileService SDK. Check out the complete documentation about App Service Authentication/Authorization here.
You will reach the point where it mentions:
Your application can also obtain additional user details through an
HTTP GET on the /.auth/me endpoint of your application. A valid token
that's included with the request will return a JSON payload with
details about the provider that's being used, the underlying provider
token, and some other user information. The Mobile Apps server SDKs
provide helper methods to work with this data.
So, in your Xamarin, after the user is successfully authentication, you have to explicitly make a HTTP GET request to /.auth/me and parse the result to get all information about the logged-in user.
Not sure how to do this in Xamarin, but here is how to do it in C# UWP (Universal Windows Platform):
var url = App.MobileService.MobileAppUri + "/.auth/me";
var clent = new Windows.Web.Http.HttpClient();
clent.DefaultRequestHeaders.Add("X-ZUMO-AUTH", this.user.MobileServiceAuthenticationToken);
var userData = await clent.GetAsync(new Uri(url));
at the of this code execution, the userData varibale will be a JSON srting with all user's claims.
Related
I’m developing a UWP application that calls an API. The API is made of an Azure Function triggered by HTTP requests. I want the Azure Function to be secured through Azure Active Directory. To do so, I created two app registrations in AAD, one for the UWP and one for the API. Both support accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts (e.g., Skype, Xbox). The API app registration provides scope, and the UWP app registration uses that scope. The code I use on my UWP is:
var HttpClient _httpClient = new HttpClient();
const string clientId = "[UWP app registration’s client ID]";
const string authority = "https://login.microsoftonline.com/[Tenant ID of the UWP app registration]";
string[] scopes = { "api://[API app registration’s client ID]/[scope]" };
var app = PublicClientApplicationBuilder
.Create(clientId)
.WithAuthority(authority)
.WithRedirectUri("https://login.microsoftonline.com/common/oauth2/nativeclient")
.Build();
AuthenticationResult result;
var accounts = await app.GetAccountsAsync();
try {
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault()).ExecuteAsync();
}
catch (MsalUiRequiredException) {
try {
result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
}
catch (Exception exception) {
Console.WriteLine(exception);
throw;
}
}
if (result == null) return;
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
var response = _httpClient.GetAsync("[API URL]").Result;
This code works, but if I replace the authority with https://login.microsoftonline.com/common (as specified here), being my app registrations multi-tenant, I get a 401 response when calling the API _httpClient.GetAsync("[API URL]").Result. The docs say the code must be updated somehow when using the /common endpoint, but I don’t understand how I should edit it. I also tried to follow these tips, but without success, while these seem not to be related to my case since I’m not building an IWA. If I run the working version of the code, result is populated with an object whose TenantId property gets the right value of the tenant that owns the app registrations while using the not-working version of the code, result is populated with an object whose TenantId property gets a value I don’t know where it’s coming from.
Can anyone help me, please?
Here's my understanding of AAD multitenancy flow :
The common authority can't be used to get a token. It's used as a common endpoint to get the templated server metadata :
v1 : https://login.microsoftonline.com/common/.well-known/openid-configuration
v2 : https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
A token should be requested from the issuer where the client is defined.
But the common authority can be used in a multitenant API (eg your Azure Functions API) to verify that a client has a valid AAD token. From the documentation :
Because the /common endpoint doesn’t correspond to a tenant and isn’t an issuer, when you examine the issuer value in the metadata for /common it has a templated URL instead of an actual value : https://sts.windows.net/{tenantid}/
Therefore, a multi-tenant application can’t validate tokens just by matching the issuer value in the metadata with the issuer value in the token. A multi-tenant application needs logic to decide which issuer values are valid and which are not based on the tenant ID portion of the issuer value.
I am attempting to write a connect app that will receive a set of data from an external source and put it inside an instance of microsoft dynamics 365 business central via its APIs. Documentation says there are two ways to do this, using basic authentication and logging in via Azure Active Directory. The former is easy and straightforward to do programmatically, but the documentation makes it very clear that it is not meant for production environments. I'm capable of doing the latter using Postman, but part of the process involves me typing in credentials in a popup window. Since the use case for the final product will be to run without user interaction, this won't do. I want the application to handle the credentials of what will be a service account by itself.
I'm able to modify records using basic authentication, and active directory if I fill out the login form when prompted. I've tried using a library called ADAL, but passing my account's credentials that way led to the following response: {"error":"invalid_request","error_description":"AADSTS90014: The request body must contain the following parameter: 'client_secret or client_assertion.}
I have access to the client secret, but there seems to be no means of passing it via ADAL, that I've found.
I've also tried, at a colleague's recommendation, to log in using the client id and client secret as username and password. The following code is what we ended up with:
RestClient client = new RestClient("https://login.windows.net/[my tenant domain]/oauth2/token?resource=https://api.businesscentral.dynamics.com");
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("undefined", "grant_type=%20client_credentials&client_id=%20[my client id]&client_secret=[my client secret]&resource=[my resource]", ParameterType.RequestBody);
string bearerToken = "";
try
{
bearerToken = JsonConvert.DeserializeObject<Dictionary<string, string>>(client.Execute(request).Content)["access_token"];
Console.WriteLine(bearerToken);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
The code above successfully retrieves a token, but if I use that token I get the following response:
<error xmlns="http://docs.oasis-open.org/odata/ns/metadata"><code>Unauthorized</code><message>The credentials provided are incorrect</message></error>
I've never used Microsoft dynamics 365. But I've validated an user using a local active directory server using C# code.
using System.DirectoryServices.AccountManagement;
public class ActiveDirectoryService {
// The domain url is the url of the active directory server you're trying to validate with.
public bool ValidateWithActiveDirectoryAsync(string domainUrl, string userName, string password) {
using (var context = new PrincipalContext(ContextType.Domain, domainUrl)) {
UserPrincipal UserPrincipal1 = new UserPrincipal(context);
PrincipalSearcher search = new PrincipalSearcher(UserPrincipal1);
if (context.ValidateCredentials(userName, password)) {
return true;
}
}
return false;
}
}
I hope it works for you.
I'm working on a ASP.NET MVC 5 with Entity Framework (version 6.0) application.
I have added the simple google login, that saves the google email with the user on registration. How do I also get the profile picture of the Google+ user when they login and cast it in a view?
Google Plus API for developers allows you to fetch public data from Google+.
Followed by detail tutorial of all the necessary steps one need to perform to successfully fetch public data from Google+.
Google implies a limit to the usage of Google+ API - Each developer has a quota. We will see about that when we will discuss Google API console.
Google uses OAuth2.0 protocol to authorize your application when it tries to access user data.
It mostly uses standard HTTP method by means of RESTful API design to fetch and manipulate user data.
Google uses JSON Data Format to represent the resources in the API.
Step1: Generate an API key through Google API Console.
Step2: used GoogleOAuth2AuthenticationOptions which means you'll need to set
up a project at https://console.developers.google.com/project first to get a ClientId and ClientSecret.
At that link (https://console.developers.google.com/project), create a project and then select it.
Then on the left side menu, click on "APIs & auth".
Under "APIs", ensure you have "Google+ API" set to "On".
Then click on "Credentials" (in the left side menu).
Then click on the button "Create new Client ID".
Follow the instructions and you will then be provided with a ClientId and ClientSecret, take note of both.
var googleOptions = new GoogleOAuth2AuthenticationOptions()
{
ClientId = [INSERT CLIENT ID HERE],
ClientSecret = [INSERT CLIENT SECRET HERE],
Provider = new GoogleOAuth2AuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new Claim("urn:google:name", context.Identity.FindFirstValue(ClaimTypes.Name)));
context.Identity.AddClaim(new Claim("urn:google:email", context.Identity.FindFirstValue(ClaimTypes.Email)));
//This following line is need to retrieve the profile image
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:google:accesstoken", context.AccessToken, ClaimValueTypes.String, "Google"));
return Task.FromResult(0);
}
}
};
app.UseGoogleAuthentication(googleOptions);
//get access token to use in profile image request
var accessToken = loginInfo.ExternalIdentity.Claims.Where(c => c.Type.Equals("urn:google:accesstoken")).Select(c => c.Value).FirstOrDefault();
Uri apiRequestUri = new Uri("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + accessToken);
//request profile image
using (var webClient = new System.Net.WebClient())
{
var json = webClient.DownloadString(apiRequestUri);
dynamic result = JsonConvert.DeserializeObject(json);
userPicture = result.picture;
}
OR
var info = await signInManager.GetExternalLoginInfoAsync();
var picture = info.ExternalPrincipal.FindFirstValue("pictureUrl");
ExternalLoginCallback method I check for which login provider is being used and handle the data for Google login.
Go through the link to get more information.
https://developers.google.com/identity/protocols/OAuth2
I have tried it its working.
I've created a Web API which uses Azure Active Directory for its authentication. It uses a multi-tenant AAD. To test it, I also created a console app which uses the ADAL library to authenticate against AAD so I can access my API. In the main AAD tenant all is working well, because I don't need to grant anything. But when accessing the app from a second tenant, I first trigger the admin consent flow (adding a prompt=admin_consent). But when I exit and open the app again, if I try to login with a user with no admin rights on the AAD, it tries to open the user consent and it fails (because the users don't have right to allow access to the AAD). If I already given admin consent, shouldn't the users already be consented?
The code for the test app is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Authentication;
using System.Threading.Tasks;
using System.Web;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Newtonsoft.Json;
namespace TestConsole
{
internal class Program
{
private const string _commonAuthority = "https://login.microsoftonline.com/common/";
private static void Main(string[] args)
{
ConsoleKeyInfo kinfo = Console.ReadKey(true);
AuthenticationContext ac = new AuthenticationContext(_commonAuthority);
while (kinfo.Key != ConsoleKey.Escape)
{
if (kinfo.Key == ConsoleKey.A)
{
AuthenticationResult ar = ac.AcquireToken("https://babtecportal.onmicrosoft.com/Portal2015.Api", "client_id", new Uri("https://out.es"), PromptBehavior.Auto, UserIdentifier.AnyUser, "prompt=admin_consent");
}
else if (kinfo.Key == ConsoleKey.C)
{
Console.WriteLine("Token cache length: {0}.", ac.TokenCache.Count);
}
else if (kinfo.Key == ConsoleKey.L)
{
ac.TokenCache.Clear();
HttpClient client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, _commonAuthority + "oauth2/logout?post_logout_redirect_uri=" + HttpUtility.UrlEncode("https://out.es"));
var response=client.SendAsync(request).Result;
Console.WriteLine(response.StatusCode);
ac=new AuthenticationContext(_commonAuthority);
}
else
{
int num;
if (int.TryParse(Console.ReadLine(), out num))
{
try
{
AuthenticationResult ar = ac.AcquireToken("https://babtecportal.onmicrosoft.com/Portal2015.Api", "client_id", new Uri("http://out.es"),PromptBehavior.Auto,UserIdentifier.AnyUser);
ac = new AuthenticationContext(ac.TokenCache.ReadItems().First().Authority);
// Call Web API
string authHeader = ar.CreateAuthorizationHeader();
HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, string.Format("http://localhost:62607/api/Values?num={0}", num));
request.Headers.TryAddWithoutValidation("Authorization", authHeader);
HttpResponseMessage response = client.SendAsync(request).Result;
if (response.IsSuccessStatusCode)
{
string responseString = response.Content.ReadAsStringAsync().Result;
Values vals = JsonConvert.DeserializeObject<Values>(responseString);
Console.WriteLine("Username: {0}", vals.Username);
Console.WriteLine("Name: {0}", vals.FullName);
vals.Range.ToList().ForEach(Console.WriteLine);
}
else
{
Console.WriteLine("Status code: {0}", response.StatusCode);
Console.WriteLine("Reason: {0}", response.ReasonPhrase);
}
}
catch (AdalException ex)
{
Console.WriteLine(ex.Message);
}
}
}
kinfo = Console.ReadKey(true);
}
}
}
public class Values
{
public string Username { get; set; }
public string FullName { get; set; }
public IEnumerable<int> Range { get; set; }
}
}
Your test app is a native client. In OAuth terms it is a public client. Those terms apply to any client that does not have a client secret or certificate credential of its own. The admin consent feature does not apply to native clients and only works for web applications. Ideally, there would be an error returned when admin consent is attempted for a native app that would indicate that the combination is not supported. We are going to look in to returning such an error in the future to prevent this kind of confusion.
In the meantime, there is no way to prevent users from seeing the consent dialogue when they sign in to a native client.
The situation is somewhat more complicated if the native app is calling a web api where both the native app and web api are owned by the same vendor/tenant. If this is set up correctly then the user will see a combined consent dialog that allows the user to consent to both the native app as well as the web api. The consent to the web api will be recorded permanently. The consent to the native app will only apply to that sign in session in the same way it would if no web api were involved. If a web api is involved in this way then admin consent can be invoked. The admin can then consent to the web api on behalf of all users. However, individual users will still need to consent to the native app.
To correctly set up this consent chain you need to use the 'knownClientApplication' attribute in the application manifest of the web api. You set the value of this attribute to the client id of the native app. You can see this being done in this sample:
https://github.com/AzureADSamples/NativeClient-WebAPI-MultiTenant-WindowsStore/blob/master/README.md
Essentially you download the application manifest through the portal, update this particular value, and then upload it.
There is some more comprehensive documentation on these topics here:
https://msdn.microsoft.com/en-us/library/azure/dn132599.aspx
Update:
One of the stipulations in the above explanation of a native app calling a web api was that they both had to be in the same tenant. If they are not in the same tenant then things get more complicated. This is the case when an ISV has created a web API that they want to make available to apps written by customers. In order for an app to get a token for a resource both apps must be registered in the same tenant. Thus, the first thing the customer will need to do is get the web api registered in their own tenant. If the web api is in the app gallery then they simply go there and install the app. The ISV does not have to have their app in the app gallery to allow customers to register it, but registration gets more complicated. The ISV will need to create a web site, registered in the ISV tenant, that the customer admin can visit. That website needs sign in the admin to get a token for the web api in a way that will trigger the consent process. Once that is complete, then the api will be registered in the customer tenant and available to customer apps.
To get your app in to the app gallery follow the instructions near the bottom of this page:
http://azure.microsoft.com/en-us/marketplace/active-directory/
I have worked with OAuth before (working with Twitter and PHP) and it was simple. I am trying to get OAuth to work with the EverNote API sample https://github.com/evernote/evernote-sdk-csharp (because, as they say, "Real applications authenticate with Evernote using OAuth"). I looked at these:
Simple C# Evernote API OAuth example or guide?
https://github.com/sethhitch/csharp-oauth-sample
http://blog.stevienova.com/2008/04/19/oauth-getting-started-with-oauth-in-c-net/
But, I still don't know how to do this... This is my code:
// Real applications authenticate with Evernote using OAuth, but for the
// purpose of exploring the API, you can get a developer token that allows
// you to access your own Evernote account. To get a developer token, visit
// https://sandbox.evernote.com/api/DeveloperToken.action
String authToken = "myAuthCode";
if (authToken == "your developer token") {
Console.WriteLine("Please fill in your developer token");
Console.WriteLine("To get a developer token, visit https://sandbox.evernote.com/api/DeveloperToken.action");
return;
}
How can I add OAuth to this to get my authToken?
Thank you.
Check this sample project : http://discussion.evernote.com/topic/30584-here-is-a-net-oauth-assembly/ . I think this will help you to understand how oauth works.
For anyone trying to get this to work in MVC, I was playing around with Evernote, OpenAuth and C# this morning and managed to get it all working. I have put together a blog post / library explaining the experience and outlining how to do it with MVC here - http://www.shaunmccarthy.com/evernote-oauth-csharp/ - it uses the AsyncOAuth library: https://github.com/neuecc/AsyncOAuth
I wrote a wrapper around AsyncOAuth that you might find useful here: https://github.com/shaunmccarthy/AsyncOAuth.Evernote.Simple
One prickly thing to be aware of - the Evernote Endpoints (/oauth and /OAuth.action) are case sensitive
// Download the library from https://github.com/shaunmccarthy/AsyncOAuth.Evernote.Simple
// Configure the Authorizer with the URL of the Evernote service,
// your key, and your secret.
var EvernoteAuthorizer = new EvernoteAuthorizer(
"https://sandbox.evernote.com",
"slyrp-1234", // Not my real id / secret :)
"7acafe123456badb123");
// First of all, get a request token from Evernote - this causes a
// webrequest from your server to Evernote.
// The callBackUrl is the URL you want the user to return to once
// they validate the app
var requestToken = EvernoteAuthorizer.GetRequestToken(callBackUrl);
// Persist this token, as we are going to redirect the user to
// Evernote to Authorize this app
Session["RequestToken"] = requestToken;
// Generate the Evernote URL that we will redirect the user to in
// order to
var callForwardUrl = EvernoteAuthorizer.BuildAuthorizeUrl(requestToken);
// Redirect the user (e.g. MVC)
return Redirect(callForwardUrl);
// ... Once the user authroizes the app, they get redirected to callBackUrl
// where we parse the request parameter oauth_validator and finally get
// our credentials
// null = they didn't authorize us
var credentials = EvernoteAuthorizer.ParseAccessToken(
Request.QueryString["oauth_verifier"],
Session["RequestToken"] as RequestToken);
// Example of how to use the credential with Evernote SDK
var noteStoreUrl = EvernoteCredentials.NotebookUrl;
var noteStoreTransport = new THttpClient(new Uri(noteStoreUrl));
var noteStoreProtocol = new TBinaryProtocol(noteStoreTransport);
var noteStore = new NoteStore.Client(noteStoreProtocol);
List<Notebook> notebooks = client.listNotebooks(EvernoteCredentials.AuthToken);
You can also try the OAuth library found here : https://code.google.com/p/devdefined-tools/wiki/OAuth and follow the steps mentioned here.
The simple code to add is:
EvernoteOAuth oauth = new EvernoteOAuth(EvernoteOAuth.HostService.Sandbox, myConsumerKey, myConsumerSecret);
string errResponse = oauth.Authorize();
if (errResponse.Length == 0)
{
Console.WriteLine(string.Format("Token: {0}\r\n\r\nExpires: {1}\r\n\r\nNoteStoreUrl: {2}\r\n\r\nUserId: {3}\r\n\r\nWebApiUrlPrefix: {4}", oauth.Token, oauth.Expires, oauth.NoteStoreUrl, oauth.UserId, oauth.WebApiUrlPrefix));
}
else
{
Console.WriteLine("A problem has occurred in attempting to authorize the use of your Evernote account: " + errResponse);
}
You will need to use this assembly:
using EvernoteOAuthNet;
Available here:
http://www32.zippyshare.com/v/98249023/file.html