Android samples files for Azure B2C require configuration file information to be replicated in B2CConfiguration class as well - azure-ad-b2c

I was implementing Azure AD B2C in Multiuser mode and was reading the sample files. Why is there a configuration class which states:
"If you'd like to use your own app registration, you will also need to update B2CConfiguration.java to match with your configuration json file."
Doesn't that seem to defeat the purpose of having a configuration file? Shouldn't the values be accessible through the module somehow as long as the configuration file is
This code shows the calling of the json configuration file:
// Creates a PublicClientApplication object with res/raw/auth_config_single_account.json
PublicClientApplication.createMultipleAccountPublicClientApplication(getContext(),
R.raw.auth_config_b2c,
new IPublicClientApplication.IMultipleAccountApplicationCreatedListener() {
#Override
public void onCreated(IMultipleAccountPublicClientApplication application) {
b2cApp = application;
loadAccounts();
}
#Override
public void onError(MsalException exception) {
displayError(exception);
removeAccountButton.setEnabled(false);
runUserFlowButton.setEnabled(false);
acquireTokenSilentButton.setEnabled(false);
}
});
And the B2CConfiguraiton shows:
/**
* Name of your B2C tenant hostname.
*/
final static String azureAdB2CHostName = "fabrikamb2c.b2clogin.com";
/**
* Name of your B2C tenant.
*/
final static String tenantName = "fabrikamb2c.onmicrosoft.com";
/**
* Returns an authority for the given policy name.
*
* #param policyName name of a B2C policy.
*/
public static String getAuthorityFromPolicyName(final String policyName) {
return "https://" + azureAdB2CHostName + "/tfp/" + tenantName + "/" + policyName + "/";
}
/**
* Returns an array of scopes you wish to acquire as part of the returned token result.
* These scopes must be added in your B2C application page.
*/
public static List<String> getScopes() {
return Arrays.asList(
"https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read");
}
All of these values are in the configuration file, except for scopes.
Is there another option here so I don't need to hard code configuration information?

The configuration details (like tenentid, policy name)can't be rendered dynamically.
In the B2CConfiguration.java file if you see the comments section it was mentioned as The value in this class has to map with the json configuration file (auth_config_b2c.json).

Related

Usage of the /common endpoint is not supported for such applications created after '10/15/2018' issue

Similar issue here. I have checked the answer and try to implement all the possible forms of link in my startup.cs class with the following code:
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithTenantId(tenantId)
.WithClientSecret(appSecret)
.WithAuthority(Authority) // Authority contains the link as mentioned in the page(link attached above)
.Build();
I still get the similar error:
"OpenIdConnectMessage.Error was not null, indicating an error. Error: 'invalid_request'. Error_Description (may be empty): 'AADSTS50194: Application 'xxx-xxx-xxx-xxx-xxxx'(ASPNET-Quickstart) is not configured as a multi-tenant application. Usage of the /common endpoint is not supported for such applications created after '10/15/2018'. Use a tenant-specific endpoint or configure the application to be multi-tenant.
Trace ID: xxx-xxx-xxx-xxx-xxxx
Correlation ID: xxx-xxx-xxx-xxx-xxxx
Timestamp: 2022-06-11 05:33:24Z'. Error_Uri (may be empty): 'error_uri is null'."
The combination of links I have used in variable Authority are the following: "https://login.microsoftonline.com/MY_TENANT_NAME" and "https://login.microsoftonline.com/MY_TENANT_ID"
I am being redirect to login page but after entering credentials OnAuthenticationFailedAsync method is being executed. This is the code of my startup class:
[assembly: OwinStartup(typeof(Web.Startup))]
namespace Web
{
public partial class Startup
{
// Load configuration settings from PrivateSettings.config
private static string appId = ConfigurationManager.AppSettings["ida:AppId"];
private static string appSecret = ConfigurationManager.AppSettings["ida:AppSecret"];
private static string redirectUri = ConfigurationManager.AppSettings["ida:RedirectUri"];
private static string graphScopes = ConfigurationManager.AppSettings["ida:AppScopes"];
private static string tenantId = ConfigurationManager.AppSettings["ida:tenantId"];
private static string aadInstance = EnsureTrailingSlash(ConfigurationManager.AppSettings["ida:AADInstance"]);
public static string Authority = "https://graph.microsoft.com/"+ tenantId;
string graphResourceId = "https://graph.microsoft.com/";
public void Configuration(IAppBuilder app)
{
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=316888
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = appId,
Authority = "https://login.microsoftonline.com/common/v2.0",
Scope = $"openid email profile offline_access {graphScopes}",
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
TokenValidationParameters = new TokenValidationParameters
{
// For demo purposes only, see below
ValidateIssuer = true
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = OnAuthenticationFailedAsync,
AuthorizationCodeReceived = OnAuthorizationCodeReceivedAsync
}
}
);
}
private static Task OnAuthenticationFailedAsync(AuthenticationFailedNotification<OpenIdConnectMessage,
OpenIdConnectAuthenticationOptions> notification)
{
notification.HandleResponse();
string redirect = $"/Home/Error?message={notification.Exception.Message}";
if (notification.ProtocolMessage != null && !string.IsNullOrEmpty(notification.ProtocolMessage.ErrorDescription))
{
redirect += $"&debug={notification.ProtocolMessage.ErrorDescription}";
}
notification.Response.Redirect(redirect);
return Task.FromResult(0);
}
private async Task OnAuthorizationCodeReceivedAsync(AuthorizationCodeReceivedNotification notification)
{
var idClient = ConfidentialClientApplicationBuilder.Create(appId)
.WithRedirectUri(redirectUri)
.WithTenantId(tenantId)
.WithClientSecret(appSecret)
.WithAuthority(Authority)
.Build();
string email = string.Empty;
try
{
string[] scopes = graphScopes.Split(' ');
var result = await idClient.AcquireTokenByAuthorizationCode(
scopes, notification.Code).ExecuteAsync();
email = await GraphHelper.GetUserDetailsAsync(result.AccessToken);
}
catch (MsalException ex)
{
System.Diagnostics.Trace.TraceError(ex.Message);
}
notification.HandleResponse();
notification.Response.Redirect($"/Account/SignInAzure?email={email}");
}
private static string EnsureTrailingSlash(string value)
{
if (value == null)
{
value = string.Empty;
}
if (!value.EndsWith("/", StringComparison.Ordinal))
{
return value + "/";
}
return value;
}
}
}
My application is for single tenant so please don't suggest me to change the setting and make it for multi-tenant.
Please check below points:
After trying to change it to specific tenant i.e.;
After changing to Ex: - https://login.microsoftonline.com/contoso.onmicrosoft.com (or tenant id),
please save changes ,refresh portal / everything and try again.
If still it shows the error , check if the Application is registered to the Azure AD Tenant as Multi Tenant Application.
Then if it still remains check if the account is actually on Azure
AD ,as this error can occur when the user credentials you are trying
to use does not belong to the same tenant where the application is
actually registered in.
If it is different tenant and you are trying to access from different
account, then you may need to change its supported account types to
any organizational directory or you need to check for correct
credentials. If not check everything or create a new app registration
.
Also please check this "Use a tenant-specific endpoint or configure the application to be multi-tenant" when signing into my Azure website for possible
ways to solve the issue.
Else you can raise a support request
References:
msal - MsalException: Applicationis not configured as a multi-tenant
application. Android - Stack Overflow
Use single-tenant Azure AD apps with Microsoft Graph Toolkit -
Waldek Mastykarz

Unable to retrieve access token using MSAL for Android

I've been trying to integrate my Android Application with Azure AD B2C using MSAL but I can't figure out how to retrieve my access token using a custom scope. I'm using this MSAL samples as a reference.
I already granted my application the needed permissions but i still get this message:
No access is found for scopes: https://rbxsoftb2c.onmicrosoft.com/rbx/get_access_token
Here's my Azure B2C settings, MSAL Constants and the API I'm trying to access :
MSAL Constants :
public class Constants {
/* Azure AD b2c Configs */
final static String AUTHORITY = "https://login.microsoftonline.com/tfp/%s/%s";
final static String TENANT = "rbxsoftb2c.onmicrosoft.com";
final static String CLIENT_ID = "MY_CLIENT_ID";
final static String SCOPES = "https://rbxsoftb2c.onmicrosoft.com/rbx/get_access_token";
final static String API_URL = "https://rbxsoft.azurewebsites.net/api/values";
final static String SISU_POLICY = "B2C_1_SiUpIn";
final static String EDIT_PROFILE_POLICY = "B2C_1_edit_profile";
}
My API :
[Produces("application/json")]
[Route("api/Values")]
[Authorize]
public class ValuesController : Controller
{
CosmosDatabaseConnection cdc = CosmosDatabaseConnection.Instance;
// GET: api/Values
[HttpGet]
public JValue Get()
{
AppState appState = SessionManager.GetCookieSession(HttpContext.Session, User);
return new JValue ( appState.User.profile.ToString() );
}
}

Xamarin Forms and Azure A2BC wrong login page

I have been trying to use Azure AD B2C with my Xamaerin.Forms iphone application. I've got it to sort of work following along based on this sample: active directory b2c xamarin native
The sample, though takes me to a login page that seems to only accept Microsoft Logins like this one:
This page seems to only let people log in with existing Microsoft accounts. I have set up my app to accept local email accounts, and I want the sign in page to look more like the link provided on the Azure AD B2C page:
This second version is the part of the login page that is displayed when using the "run now endpoint" on the AD B2C signin signup policy that looks as follows: https://login.microsoftonline.com/crowdwisdom.onmicrosoft.com/oauth2/v2.0/authorize?p=B2C_1_susi&client_id=0729f822-6c97-4b94-b75c-df4259b0f3c5&nonce=defaultNonce&redirect_uri=https%3A%2F%2Flogin.crowdwisdom.co&scope=openid&response_type=id_token&prompt=login
I don't understand which parameter of the AcquireTokenAsync method determines which page is delivered to the app
Here is the code I run that results in the top example:
public async void HandleSignIn()
{
try
{
AuthenticationResult ar = await App.PCA.AcquireTokenAsync(Constants.Scopes, GetUserByPolicy(App.PCA.Users, Constants.PolicySignUpSignIn), Constants.UiParent);
}
catch (Exception ex)
{
// Checking the exception message
// should ONLY be done for B2C
// reset and not any other error.
if (ex.Message.Contains("AADB2C90118"))
HandlePasswordReset();
// Alert if any exception excludig user cancelling sign-in dialog
else if (((ex as MsalException)?.ErrorCode != "authentication_canceled"))
throw ex;
}
}
private IUser GetUserByPolicy(IEnumerable<IUser> users, string policy)
{
foreach (var user in users)
{
string userIdentifier = Base64UrlDecode(user.Identifier.Split('.')[0]);
if (userIdentifier.EndsWith(policy.ToLower())) return user;
}
return null;
}
Constants definition:
public static class Constants
{
public static string Tenant = "foo.onmicrosoft.com";
public static string ClientID = "0729...-..."; //actual client id here.
public static string PolicySignUpSignIn = "B2C_1_susi";
public static string PolicyEditProfile = "B2C_1_edit_profile";
public static string PolicyResetPassword = "B2C_1_reset";
public static string[] Scopes = { "User.read" };
public static string ApiEndpoint = "https://foo.azurewebsites.net";
public static string AuthorityBase = $"https://login.microsoftonline.com/{Tenant}/oauth2/v2.0/authorize?p=";
private static string suffix = $"&client_id={ClientID}&nonce=defaultNonce&redirect_uri=https%3A%2F%2Fmyapi&scope=openid&response_type=id_token&prompt=login";
public static string Authority = $"{AuthorityBase}{PolicySignUpSignIn}{suffix}";
public static string AuthorityEditProfile = $"{AuthorityBase}{PolicyEditProfile}";
public static string AuthorityPasswordReset = $"{AuthorityBase}{PolicyResetPassword}";
public static UIParent UiParent = null;
}
Thanks for posting the code. When you use MSAL with AAD B2C, it's important that you indicate which policy you wish to use. That's how MSAL knows to invoke B2C functionality. What's happening right now is that you're not properly indicating which policy to use, and MSAL is defaulting back to the regular Microsoft login page, which only allows Microsoft personal & work/school accounts.
When using MSAL, the proper way to indicate policy is to use tfp in the path of the authority, like:
string BaseAuthority = "https://login.microsoftonline.com/tfp/mytenant.onmicrosoft.com/mypolicy";
See https://github.com/Azure-Samples/active-directory-b2c-xamarin-native/blob/master/UserDetailsClient/UserDetailsClient/App.cs#L25 for the most up-to-date example.
Yes, you can also use the p query string parameter to indicate policy, but the way you are passing it to MSAL causes MSAL to ignore it's existence and not include it in OAuth requests.
Last comment: you shouldn't have to deal with all those OAuth parameters in your suffix variable. MSAL will take care of that stuff for you.

Obtaining authorization token from Azure AD with Dotnetopenauth

I am trying to obtain authorization token from Azure AD through DotNetOpenAuth library. I do not want to use ADAL because I have a huge project in .net 3.5 and ADAL does not supports .net 3.5 (only .net > 4). However, I can't quite get it to work with Azure AD. I do not know what to configure. So far, this is what I have:
private static WebServerClient _webServerClient;
private static string _accessToken;
// Client ID (as obtained from Azure AD portal)
private static string clientId = "here goes my client id guid";
// Client Secret (as obtained from Azure AD portal)
private static string appKey = "here goes my secret";
private static string aadInstance = "https://login.microsoftonline.com/{0}";
private static string tenant = "mytenant.domain.com";
private static string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
// Azure AD resource I am trying to access
private static string serviceResourceId = "https://mytenant.domain.com/protectedresource";
private static void InitializeWebServerClient()
{
var authorizationServer = new AuthorizationServerDescription
{
AuthorizationEndpoint = new Uri(""/* WHAT TO PUT HERE */),
TokenEndpoint = new Uri(""/* WHAT TO PUT HERE */)
};
_webServerClient = new WebServerClient(authorizationServer, clientId, appKey);
}
private static void RequestToken()
{
var state = _webServerClient.GetClientAccessToken();
_accessToken = state.AccessToken;
}
static void Main(string[] args) {
InitializeWebServerClient();
RequestToken();
}
The problem is I do not know what to place here. I do not know what values I should place here:
AuthorizationEndpoint = new Uri(""/* WHAT TO PUT HERE */),
TokenEndpoint = new Uri(""/* WHAT TO PUT HERE */)
Check if this GitHub sample assists you with authentication. It has 3 methods for authenticating and acquiring authentication token with detailed instructions. Check the app.config for sample values and method comments for details on what is required.
Link to the sample: Azure Authentication GitHub Sample
Related blog for the sample: Azure Authentication - Authenticating any Azure API Request in your Application
I believe the two endpoints you want are:
https://login.windows.net/{{tenantId}}/oauth2/authorize
https://login.windows.net/{tenantId}/oauth2/token
Where {tenantId} is your tenant's GUID identifier. It may also work with your domain as well, but I haven't checked that.

What is the XsrfKey used for and should I set the XsrfId to something else?

In my MVC 5 web app I have this (in AccountController.cs):
// Used for XSRF protection when adding external sign ins
private const string XsrfKey = "XsrfId";
and
public string SocialAccountProvider { get; set; }
public string RedirectUri { get; set; }
public string UserId { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, SocialAccountProvider);
}
How exactly is it being used for protection?
Should I set the value of XsrfKey to something more random?
Take a look at ManageController methods LinkLogin and LinkLoginCallback:
//
// POST: /Manage/LinkLogin
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider)
{
// Request a redirect to the external login provider to link a login for the current user
return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId());
}
//
// GET: /Manage/LinkLoginCallback
public async Task<ActionResult> LinkLoginCallback()
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
if (loginInfo == null)
{
return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
}
var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
return result.Succeeded ? RedirectToAction("ManageLogins") : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
}
These are the methods that handle linking of external accounts (i.e. Google, Facebook, etc.). The flow goes like this:
User clicks "Link Account" button, which calls a POST to LinkLogin method.
LinkLogin returns ChallengeResult object, with callback url set to LinkLoginCallback method.
ChallengeResult.ExecuteResult is called by MVC framework, calls IAuthenticationManager.Challenge, which causes a redirect to the specific external login provider (let's say: google).
User authenticates with google, then google redirects to callback url.
The callback is handled with LinkLoginCallback. Here, we want to prevent XSRF and verify that the call was initiated by a user, from a page served by our server (and not by some malicious site).
Normally, if it was a simple GET-POST sequence, you would add a hidden <input> field with an anti-forgery token and compare it with a corresponding cookie value (that's how Asp.Net Anti-Forgery Tokens work).
Here, the request comes from external auth provider (google in our example). So we need to give the anti-forgery token to google and google should include it in the callback request. That's exactly what state parameter in OAuth2 was designed for.
Back to our XsrfKey: everything you put in AuthenticationProperties.Dictionary will be serialized and included in the state parameter of OAuth2 request - and consequentially, OAuth2 callback. Now, GetExternalLoginInfoAsync(this IAuthenticationManager manager, string xsrfKey, string expectedValue) will look for the XsrfKey in the received state Dictionary and compare it to the expectedValue. It will return an ExternalLoginInfo only if the values are equal.
So, answering your original question: you can set XsrfKey to anything you want, as long as the same key is used when setting and reading it. It doesn't make much sense to set it to anything random - the state parameter is encrypted, so no one expect you will be able to read it anyway.
Just leave it as is:
As the name of the member states it is a key:
private const string XsrfKey = "XsrfId";
It is defined in this manner to avoid "magic numbers" and then is used a little down in the scaffold code:
public override void ExecuteResult(ControllerContext context)
{
var properties = new AuthenticationProperties { RedirectUri = RedirectUri };
if (UserId != null)
{
properties.Dictionary[XsrfKey] = UserId;
}
context.HttpContext.GetOwinContext().Authentication.Challenge(properties, LoginProvider);
}
The value of the dictionary item is then set to the UserId property in the above code by using the XsrfKey member as the key.
IOW the code is already setting the XSRF dictionary item to the value of the user ID in the snippet. If you change the XsrfKey members value to anything else you will cause problems down the line, since the expected key "XsrfId" will have no value set.
If by changing it to something more random you are implying to change the value and not they key of the dictionary, or in other words, not set it to the user id then please see the following for an explanation of the anti forgery token inner workings.
http://www.asp.net/mvc/overview/security/xsrfcsrf-prevention-in-aspnet-mvc-and-web-pages

Resources