Microsoft Azure B2C - Get Profile Information - c#

I am moments from giving up entirely on this workflow.
I set up an Azure account with a REST API, a few other things, and then a b2c AD. After hours and hours of hating my life, I finally have it (somewhat) working to use this AD to sign in, and to be required to access the REST API.
The problem I am now having is that I have tried every single variation of anything I can find only about how to get the profile information of that person who is logged in, and I am getting absolutely nothing.
Even the example files after login don't properly work - it is meant to redirect you to a page that says welcome (username) but the username does not return in my result to
result = await App.AuthenticationClient
.AcquireTokenInteractive(B2CConstants.Scopes)
.WithPrompt(Microsoft.Identity.Client.Prompt.SelectAccount)
.WithParentActivityOrWindow(App.UIParent)
.ExecuteAsync();
None of this is the least bit intuitive. I have read docs but somehow every time I google it I end up on a different page of Microsoft docs with different information.
I do not know what I need to pass to scopes... I do not know how to property get an auth token, as every time I try and use graphs I get an error about an empty or invalid auth token (even after logging in).
Even though I logged in with google successfully, the result above gives me a NULL access token, null tenant id, null scopes, null username under account... the ONLY thing that is correct is the unique ID lines up with the ID I see on the AD users page.
EDIT: the AccessToken is not null actually, I get info there... but every call I try to make that I think should use it is failing.
Ie, if I try to call this to get my info after signing in:
InteractiveAuthenticationProvider authProvider = new InteractiveAuthenticationProvider(App.AuthenticationClient, B2CConstants.Scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var users = await graphClient.Me.Request().GetAsync();
Note the App.AuthenticationClient is what I used to login above - I have also tried this with
//.Create(B2CConstants.ClientId)
//.Build();
I get this (only partial error so I don't pass out IDs)
Microsoft.Graph.ServiceException: 'Code: InvalidAuthenticationToken
Message: Access token validation failure.
Inner error:
AdditionalData:
date: 2020-10-26T21:45:25

I suggest that you mostly refer Microsoft Docs for any information.
Consider below code to get users details from B2C (This is Android Xamarin Forms page .cs code):
async void OnSignInSignOut(object sender, EventArgs e)
{
AuthenticationResult authResult = null;
IEnumerable<IAccount> accounts = await App.PCA.GetAccountsAsync();
try
{
if (btnSignInSignOut.Text == "Sign in")
{
try
{
IAccount firstAccount = accounts.FirstOrDefault();
authResult = await App.PCA.AcquireTokenSilent(App.Scopes, firstAccount)
.ExecuteAsync();
}
catch (MsalUiRequiredException ex)
{
try
{
authResult = await App.PCA.AcquireTokenInteractive(App.Scopes)
.WithParentActivityOrWindow(App.ParentWindow)
.ExecuteAsync();
}
catch(Exception ex2)
{
await DisplayAlert("Acquire token interactive failed. See exception message for details: ", ex2.Message, "Dismiss");
}
}
if (authResult != null)
{
var content = await GetHttpContentWithTokenAsync(authResult.AccessToken);
UpdateUserContent(content);
Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign out"; });
}
}
else
{
while (accounts.Any())
{
await App.PCA.RemoveAsync(accounts.FirstOrDefault());
accounts = await App.PCA.GetAccountsAsync();
}
slUser.IsVisible = false;
Device.BeginInvokeOnMainThread(() => { btnSignInSignOut.Text = "Sign in"; });
}
}
catch (Exception ex)
{
await DisplayAlert("Authentication failed. See exception message for details: ", ex.Message, "Dismiss");
}
}
For complete and step by step samples on B2C integration, you can visit below links:
Integrate Microsoft identity and the Microsoft Graph into a Xamarin forms app using MSAL
Integrate Azure AD B2C into a Xamarin forms app using MSAL

Related

How to completely log out of ASP.NET Core server application when using .NET Maui mobile application and Web Authenticator

I'm working on .NET Maui mobile client with ASP.NET Core backend, and am trying to integrate Google authorization via the Web Authenticator API built into Maui. I was able to get the login to function by basically following the guide on the Web Authenticator page linked above, but haven't been able to logout from the account I originally logged in with. I've created a logout endpoint for my server, and after I hit this endpoint (where I call HttpContext.SignOutAsync() ) and compare the value of ClaimsPrinciple, the data from Google is gone (see attached screenshots).
before logout
after logout
However, the next time I try to log in I do not need to go through Google authentication, it automatically logs me in again. I've seen similar issues with Web Authenticator (linked here and here and in some other issues linked to these). I'm new at this framework and mobile dev in general and I'm still unclear from these resources what the best way to handle this is - most of these issues are also relating to Xamarins forms rather than Maui, so not sure if theres any more updated solution.
These are the implementations of login and logout and the corresponding requests
public class MobileAuthController : ControllerBase
{
const string callbackScheme = "myapp";
[HttpGet("{scheme}")]
public async Task Get([FromRoute] string scheme)
{
var auth = await Request.HttpContext.AuthenticateAsync(scheme);
if (!auth.Succeeded
|| auth?.Principal == null
|| !auth.Principal.Identities.Any(id => id.IsAuthenticated)
|| string.IsNullOrEmpty(auth.Properties.GetTokenValue("access_token")))
{
//Not authenticated, challenge
await Request.HttpContext.ChallengeAsync(scheme);
}
else
{
var claims = auth.Principal.Identities
.FirstOrDefault().Claims.Select(claim => new
{
claim.Issuer,
claim.OriginalIssuer,
claim.Type,
claim.Value
});
//Build the result url
var user = this.User; //CHECK VALUE OF USER
var url = callbackScheme + "://#";
//Redirect to final url
Request.HttpContext.Response.Redirect(url);
}
}
[HttpPost("logout")]
public async Task Logout()
{
try
{
await HttpContext.SignOutAsync();
var url = callbackScheme + "://#";
var user = this.User; //CHECK VALUE OF USER
}
catch (Exception ex)
{
Debug.WriteLine($"Unable to sign out user {ex.Message}");
}
}
var url = "http://localhost:5000/mobileauth/logout";
HttpContent content = null;
var response = await httpClient.PostAsync(url, content);
WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
new Uri("https://localhost:5001/mobileauth/Google"),
new Uri("myapp://"));
Edit - I'm primarily confused on how i need to be handling Cookies and access/refresh tokens and the built in claims stuff - like I said, I'm new at this

Why am I getting Error Access Denied after successful interactive authentication with Microsoft Graph?

I have Microsoft Graph setup within the local application and the Azure Portal. I can sign in with my own account successfully but when another employee attempts to sign in I receive a successful authentication and access token but when InitializeGraphClientAsync() is called Microsoft.Graph.ServiceException is thrown with the following...
Exception thrown: 'Microsoft.Graph.ServiceException' in System.Private.CoreLib.dll
Failed to initialized graph client.
Accounts in the msal cache: 1.
See exception message for details: Code: ErrorAccessDenied
Message: Access is denied. Check credentials and try again.
Sign in:
public async Task<string> SignIn()
{
// First, attempt silent sign in
// If the user's information is already in the app's cache,
// they won't have to sign in again.
var message = "";
try
{
var accounts = await PCA.GetAccountsAsync();
var silentAuthResult = await PCA.AcquireTokenSilent(Scopes, accounts.FirstOrDefault()).ExecuteAsync();
Debug.WriteLine("User already signed in.");
Debug.WriteLine($"Successful silent authentication for: {silentAuthResult.Account.Username}");
Debug.WriteLine($"Access token: {silentAuthResult.AccessToken}");
message = $"Successful silent authentication for: {silentAuthResult.Account.Username}";
}
catch (MsalUiRequiredException msalEx)
{
// This exception is thrown when an interactive sign-in is required.
Debug.WriteLine("Silent token request failed, user needs to sign-in: " + msalEx.Message);
message = "Silent token request failed, user needs to sign-in: " + msalEx.Message;
// Prompt the user to sign-in
var interactiveRequest = PCA.AcquireTokenInteractive(Scopes);
if (AuthUIParent != null)
{
interactiveRequest = interactiveRequest
.WithParentActivityOrWindow(AuthUIParent);
}
var interactiveAuthResult = await interactiveRequest.ExecuteAsync();
Debug.WriteLine($"Successful interactive authentication for: {interactiveAuthResult.Account.Username}");
Debug.WriteLine($"Access token: {interactiveAuthResult.AccessToken}");
message = $"Successful interactive authentication for: {interactiveAuthResult.Account.Username}";
}
catch (Exception ex)
{
Debug.WriteLine("Authentication failed. See exception messsage for more details: " + ex.Message);
message = "Authentication failed. See exception messsage for more details: " + ex.Message;
}
await InitializeGraphClientAsync();
return message;
}
Initialize
private async Task InitializeGraphClientAsync()
{
var currentAccounts = await PCA.GetAccountsAsync();
try
{
if (currentAccounts.Count() > 0)
{
// Initialize Graph client
GraphClient = new GraphServiceClient(new DelegateAuthenticationProvider(
async (requestMessage) =>
{
var result = await PCA.AcquireTokenSilent(Scopes, currentAccounts.FirstOrDefault())
.ExecuteAsync();
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
}));
await GetUserInfo();
IsSignedIn = true;
}
else
{
IsSignedIn = false;
}
}
catch (Exception ex)
{
Debug.WriteLine("Failed to initialized graph client.");
Debug.WriteLine($"Accounts in the msal cache: {currentAccounts.Count()}.");
Debug.WriteLine($"See exception message for details: {ex.Message}");
await SignOut();
}
}
The code was lifted straight out of one of Microsoft's tutorials.
Azure:
API permissions
I have it configured as Accounts in any organizational directory (Any Azure AD directory - Multitenant)
You can try checking and following the below workarounds to resolve the issue:
Verify that the access token type that your app receives matches the type of permissions that are sought or granted.
You may be asking for and approving application permissions while using delegated interactive code flow tokens rather than client credential flow tokens, or you may be asking for and approving delegated permissions while using client credential flow tokens rather than delegated code flow tokens.
Make sure that your application is sending Microsoft Graph a valid access token as part of the request.
Based on the Microsoft Graph APIs your app calls, Check to see if the permissions you requested are accurate.
References :
Resolve Microsoft Graph authorization errors ,
Microsoft Graph permissions reference
Verify that the access token type that your app receives matches the type of permissions that are sought or granted.
It appears I must have signed in using an earlier build that had increased permissions. Checking the token given for my account vs another employee showed there were less permissions. Adding the necessary permissions eliminated the error and solved the problem.

"BotAuthenticator failed to authenticate incoming request!" error in Teams when using Bot

I've created a Bot application in Visual Studio 2017, which I want to use in MS Teams. This application is part of a solution, which contains 2 components, the bot application itself and a windows application, which I have created that is used by the bot application to retrieve an authentication token from Microsoft (using similar code to what is on this website https://learn.microsoft.com/en-us/azure/active-directory/develop/guidedsetups/active-directory-uwp-v2).
When debugging the bot after hosting it locally, I'am able to use the bot successfully in Teams. There is no error. However, now that I have registered the bot with the Microsoft Bot Framework in Azure, I'm now having issues as Teams returns back the message "Sorry, my bot code is having an issue." In Azure I have a Bots Channels Registration entity, which in its settings points to a messaging endpoint that is https://.azurewebsites.net/api/messages. I also have a Apps Service. Now I have transferred the application id that I received when registering the bot with the Microsoft Bot Framework and have put this into the bot application in Visual Studio in the web.config file along with the app password.
After testing this in the Bot Framework Emulator I get "POST 401 directline.postActivity" and in the "Inspector-JSON" I get "BotAuthenticator failed to authenticate incoming request!". This is my first bot application so I'm lost as to what I have potentially missed out so does anyone have any idea what I could try?
So here's what I have in my RootDialog.cs file, which where the endpoint will hit when the bot is used.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
string userInfo = "";
AuthTokenDeploy tokenDeploy = new AuthTokenDeploy();
userInfo = await tokenDeploy.MsGraphUserInfo();
if(!userInfo.Equals(""))
{
// send webhook to end user system
await SendToEndpoint(context, activity, activity.Text,
userInfo);
}
}
AuthTokenDeploy is an instance of another class, which is where the function to obtain the access token along with the user information from Microsoft is held. So I created a string "userInfo", which then takes the value given by MsGraphUserInfo().
public async Task<string> MsGraphUserInfo()
{
AuthenticationResult authResult = null;
string Text = null;
try
{
authResult = await App.PublicClientApp.AcquireTokenSilentAsync(_scopes, App.PublicClientApp.Users.FirstOrDefault());
}
catch (MsalUiRequiredException ex)
{
// A MsalUiRequiredException happened on AcquireTokenSilentAsync. This indicates you need to call AcquireTokenAsync to acquire a token
System.Diagnostics.Debug.WriteLine($"MsalUiRequiredException: {ex.Message}");
try
{
authResult = await App.PublicClientApp.AcquireTokenAsync(_scopes);
}
catch (MsalException msalex)
{
}
}
catch (Exception ex)
{
}
if (authResult != null)
{
Text = await GetHttpContentWithToken(_graphAPIEndpoint, authResult.AccessToken);
}
return Text;
}
Calling MsGraphUserInfo() will open the "AuthToken.exe" as a popup Window and ask the user to log in with their credentials. You can see from the code above that it acquires the access token first, which is then passed into GetHttpContentWithToken(), which is where a HTTP GET request is run against "https://graph.microsoft.com/v1.0/me" and a JSON string is returned with the user information in it.
public async Task<string> GetHttpContentWithToken(string url, string token)
{
var httpClient = new System.Net.Http.HttpClient();
System.Net.Http.HttpResponseMessage response;
try
{
var request = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, url);
//Add the token in Authorization header
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
response = await httpClient.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
return content;
}
catch (Exception ex)
{
return ex.ToString();
}
}
Now I feel as if this method of using a Windows application to allow the user to log in might not be the best way forward, hence why I've been reading and following this guide https://learn.microsoft.com/en-us/azure/app-service/app-service-web-tutorial-auth-aad. I would like to know whether it is possible to use what is on this page to allow my bot to retrieve an access token?

UWP Microsoft.Identity.Client pass login without promt

I'm accessing an Outlook calendar with the Microsoft Graph API. In my UWP App I'm using the Microsoft.Identity.Client, which is available on Nuget. This works without issues, but for the first time I want to get a users calendar, I have to sign-in. Here's my code for authenticating / getting a token
private async Task<string> GetTokenForUserAsync()
{
string tokenForUser = null;
string[] Scopes = { "https://graph.microsoft.com/Calendars.Read" };
PublicClientApplication identityClient = new PublicClientApplication(clientId);
AuthenticationResult authResult;
IEnumerable<IUser> users = identityClient.Users;
if (users.Count() > 0)
{
try
{
authResult = await identityClient.AcquireTokenSilentAsync(Scopes, users.First());
tokenForUser = authResult.AccessToken;
}
catch
{
tokenForUser = null;
}
}
else
{
try
{
authResult = await identityClient.AcquireTokenAsync(Scopes);
tokenForUser = authResult.AccessToken;
}
catch
{
tokenForUser = null;
}
}
return tokenForUser;
}
When calling this Task for the first time, I have to log in with my Outlook credentials inside some sort of WebView which gets opened. After the first request, this is not needed anymore, because identityClient.Users does contain my logged in user.
Now what I try to achieve is that I can hardcode my login and pass it to the authentication. But the only thing what I have found is the ability to provide the login username (Outlook mail address) with the AcquireTokenAsync() overload
authResult = await identityClient.AcquireTokenAsync(Scopes, "myuser#outlook.com");
But there is no overload inside this method to provide the password. So is there any other option, to pass the password to this call? The main reason why I'm using the REST API is because this app is running on Windows 10 IoT Core and there is no AppointmentStore (local calendar) available.
You can try to use the WebAccount class to store the user's account information for future use once a user has authorized your app once. Please see the Web Account Manager topic and look into the Store the account for future use part.
After trying different solutions, which didn't provide me what i'm looking for, I decided to go another way.
Now for reading a calendar, I simply use subscreibed ics / ical files, which provides nearly realtime access to a calendar without authorization.

When/where to refresh access token in Windows 10 app with azure backend

I'm learning to build Windows 10 apps with an azure backend. I'm using Micosoft Account as my authentication provider. I've learned how to cache access tokens but I'm a little hung up on refresh tokens.
As I understand it, the access token is short lived, and the longer expiring refresh token allows me to get a new access token. I've been trying to follow along with Adrian Hall's book here: https://adrianhall.github.io/develop-mobile-apps-with-csharp-and-azure/chapter2/realworld/#refresh-tokens
My problem is that I don't quite understand when/where to call or how to use "client.RefreshUserAsync();" and the book isn't really clear.
When should I call refresh?? I guess the problem is that the token might expire in the middle of the user using the app, forcing the user to login again right? So do I call refresh every time my user does anything? I'm confused.
Right now, my app just has a single AuthenticateAsync method on my mainpage that executes when a user clicks a login button. It looks for a cached token, if there is one it checks expiration and re-authenticates if expired.
private async System.Threading.Tasks.Task<bool> AuthenticateAsync()
{
string message;
bool success = false;
var provider = MobileServiceAuthenticationProvider.MicrosoftAccount;
// Use the PasswordVault to securely store and access credentials
PasswordVault vault = new PasswordVault();
PasswordCredential credential = null;
try
{
//try to get an existing credential from the vault.
credential = vault.FindAllByResource(provider.ToString()).FirstOrDefault();
}
catch (Exception)
{
//When there is no matching resource an error occurs, which we ignore.
}
if (credential != null)
{
// Create a user from the stored credentials.
user = new MobileServiceUser(credential.UserName);
credential.RetrievePassword();
user.MobileServiceAuthenticationToken = credential.Password;
// Set the user from the stored credentials.
App.MobileService.CurrentUser = user;
success = true;
message = string.Format("Cached credentials for user - {0}", user.UserId);
// Consider adding a check to determine if the token is
// expired, as shown in this post: http://aka.ms/jww5vp
//check expiration
if (App.MobileService.IsTokenExpired())
{
//remove the expired credentials
vault.Remove(credential);
try
{
// Login with the identity provider
user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
// Create and store the user credentials.
credential = new PasswordCredential(provider.ToString(),
user.UserId, user.MobileServiceAuthenticationToken);
vault.Add(credential);
message = string.Format("Expired credentials caused re-authentication. You are now signed in - {0}", user.UserId);
success = true;
}
catch (InvalidOperationException)
{
message = "You must log in. Login required.";
}
}
}
else
{
try
{
// Login with the identity provider
user = await App.MobileService.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
// Create and store the user credentials.
credential = new PasswordCredential(provider.ToString(),
user.UserId, user.MobileServiceAuthenticationToken);
vault.Add(credential);
message = string.Format("You are now signed in - {0}", user.UserId);
success = true;
}
catch (InvalidOperationException)
{
message = "You must log in. Login required.";
}
}
var dialog = new MessageDialog(message);
dialog.Commands.Add(new UICommand("OK"));
await dialog.ShowAsync();
return success;
}
I guess the problem is that the token might expire in the middle of the user using the app, forcing the user to login again right?
Based on your description, you use Azure mobile app as your UWP backend. To access mobile app we need to use access token. and as you know that, access token will be expired. In order to get a new access token, we need to use refresh token. For how to get access token by refresh token, please refer to this article. Below is detailed http request info:
// Line breaks for legibility only
POST /{tenant}/oauth2/token HTTP/1.1
Host: https://login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&refresh_token=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq...
&grant_type=refresh_token
&resource=https%3A%2F%2Fservice.contoso.com%2F
&client_secret=JqQX2PNo9bpM0uEihUPzyrh // NOTE: Only required for web apps
From above http request, we only provide client_id, refresh_token, grant_type, resource, client_secret(web app only). So we need not to let user login again.
When should I call refresh??
If the access token is expired, it will get error when we access mobile app. At this moment we can try to get a new access token by refresh token in the catch{} logic.

Categories

Resources