Azure-Samples / active-directory-b2c-xamarin-native

This is a simple Xamarin Forms app showcasing how to use MSAL to authenticate users via Azure Active Directory B2C, and access a Web API with the resulting tokens.
http://aka.ms/aadb2c
MIT License
111 stars 65 forks source link

Clarify and enhance B2C client side + web API set-up documentation #131

Open plettb opened 4 years ago

plettb commented 4 years ago

This is an excellent example, and works exactly the way I want my application to work. However, try as I might I simply can't get my configuration to work the same way. Is there any chance there the configuration of the "fabrikamb2c" could be documented?

There is a similar request on another sample project here, but it was eventually closed, still without sufficient detail.

Any help on this would be MUCH appreciated!

Thanks! Brad.

jennyf19 commented 4 years ago

@plettb thanks for the feedback. Does this documentation help get you started? It walks you through how to set up your own b2c tenant.

plettb commented 4 years ago

Does this documentation help get you started?

No, sadly, it doesn't. I'm having no trouble setting up the B2C tenant, and it mostly works, but there is clearly somethings subtly different. For example when I use my own tenant and run on Android, login appears to work, but then it never returns to the app. I know I'm close because it does return when running the UWP version.

I think it's likely screenshots of some of the important screens (e.g. the app registration's "Authentication" page might show the minor difference that I can't seem to find.

jennyf19 commented 4 years ago

@plettb i see. so it's just with getting the sample working with your specific b2c tenant. Thanks for clarifying that. Are you running the sample as-is...meaning you are just replacing the variable values with your own?

plettb commented 4 years ago

Are you running the sample as-is...meaning you are just replacing the variable values with your own?

Yes, the only thing I'm changing is "B2CConstants". That way, in theory, if I've set up my B2C tenant, together with the app registrations, etc., correctly mine should run identically.

(Well, when trying to get the API call working, I'm also having to change the endpoint URL in "App.xaml.cs". That endpoint should be moved into "B2CConstants", in my opinion, but that's very minor. If/when I get this working, I'll gladly create a pull request for that.)

jennyf19 commented 4 years ago

@plettb Yes, a PR to fix that would be great. thanks!

For Android, in the AndroidManifest.xml, you'll need to change the scheme value to be your clientId: <data android:scheme="msal[your_client_id]" android:host="auth" />

this should work now for Android.

plettb commented 4 years ago

@plettb Yes, a PR to fix that would be great. thanks!

The problem is that I'd find it difficult to fix only that issue. If/when I get time I'll do that, but also address one of the other issues in this branch: attempt to implement MVVM.

For Android, in the AndroidManifest.xml, you'll need to change the scheme value to be your clientId: <data android:scheme="msal[your_client_id]" android:host="auth" />

In the infamous words of Homer Simpson... D'oh!!! Well, I was sure it was something simple, and at least on that score I was right. Still... that feels like a rookie mistake on my part. Thanks a LOT, @jennyf19!!!

On an unrelated note, but I don't think I should raise this as an "issue", per se... Any idea why debugging the UWP app doesn't work properly for me? This works for the Android version, but with the UWP version I can debug the code in the UWP project, but it won't jump to the code in the core project.

jennyf19 commented 4 years ago

@plettb glad you got it sorted out! thanks for the quick reply.

@bgavrilMS can you assist on the debugging in UWP issue @plettb mentions above?

jennyf19 commented 4 years ago

@plettb also, take your time on the PR(s), we appreciate any outside contribution.

plettb commented 4 years ago

Apparently I wasn't quite as close as I thought. Whereas the authentication against my own B2C tenant appears to be working, it's not actually providing an access token that can be used for subsequent calls. Once again I'm curious how "fabrikamb2c" is different from my configuration. Why does it return a token and mine doesn't?!?

jennyf19 commented 4 years ago

@plettb In the sample, we have a mobile client, which is calling the node js web api (protected resource).

Because we follow the OAuth2.0 protocol, which is to gain access to protected resources through scoped access tokens, the node js web api exposes some scopes: image

Then with the mobile client, in the portal, we've given it permission to the node js web api by configuring the API permissions: image

Then in the code, you set the API endpoint: public static string ApiEndpoint = "https://fabrikamb2chello.azurewebsites.net/hello"; and scopes: public static string[] Scopes = { "https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read" }; Then, when you acquire a token interactively you tell MSAL, "I want an access token for these scopes to this resource". Then the user will get a sign-in dialogue and he/she will consent for those scopes. The B2C authorization server will then issue you an Access Token for the protected resource. But the trust relationship between the protected resource (web api) and the mobile app happens in the portal. That's why you have both registered in the portal.

So if you're not getting an Access Token back, it usually because the web api is not registered correctly, or it's not exposing scopes, or the mobile client is sending the wrong value for the scopes, so the authorization server will not issue a token.

Sorry for the long answer, but hopefully that helps.

plettb commented 4 years ago

@jennyf19 Thank-you SO MUCH!!! It was, indeed, a "scope" thing, and I was able to get it working!

I find this a bit strange, but it didn't prevent me from getting it working. In the "fabrikamb2c" configuration page you show above, notice this: image In my test environment, I get this: image I've found no way of changing "Admins only" to "Admins and users", but fortunately it doesn't seem to matter.

Now my intentions are to document all of this. What I'm not sure about is whether this issue should be closed now, or only after I provide a document from the wiki. Thoughts?

Thanks again!!!

jennyf19 commented 4 years ago

@plettb I'm glad it's working! yay!

We have this in the ReadMe, but sounds like it's either 1) no clear or 2) not updated, or both. :)

@mmacy do we have a doc which clearly shows how to connect a B2C tenant's client app + protected resource (web API), in the Azure AD B2C portal? Maybe @plettb and I can pull from that doc to make things in the ReadMe clearer. Thx.

@jmprieur helped me find this for Admins only issue...looks like in AAD they have a radio button to change this, but in B2C, you'll have to go to the manifest and edit the oauth2Permissions to have "type": "User",

    "oauth2Permissions": [
        {
            "adminConsentDescription": "read permissions",
            "adminConsentDisplayName": "read permissions",
            "id": "f6eefa1d-.....2743eb",
            "isEnabled": true,
            "lang": null,
            "origin": "Application",
            "type": "User",
            "userConsentDescription": "read permissions",
            "userConsentDisplayName": "read permissions",
            "value": "demo.read"
        },
lor-olo commented 4 years ago

I agree that it would be very beneficial to have access to all relevant Fabrikamb2c config information. I have my own B2C tenant, etc., I can log in via my Xam app, etc. but I can't seem to get the AccessToken returned upon login.

plettb commented 4 years ago

I agree that it would be very beneficial to have access to all relevant Fabrikamb2c config information. I have my own B2C tenant, etc., I can log in via my Xam app, etc. but I can't seem to get the AccessToken returned upon login.

I don't know if this will help you, but when I was not getting an AccessToken returned, it turned out to be due to the scopes. To be honest I'm not exactly sure what the problem was, but I went through all the steps to create a NEW scope, used it in my app, and it began working. Hope that helps!

jennyf19 commented 4 years ago

@lorne-olo did you see what I wrote above regarding the access token? @nickgmicrosoft we need to improve the documentation around this...do you have bandwidth?

lor-olo commented 4 years ago

@jennyf19 Yes, thanks. Your comments have helped. There are just so many moving pieces--and every person's situation is a little different. I now obtain the access token, but when I call an endpoint of my web app, the response is HTML indicating I can't be logged in. (I'm actually just trying to access API endpoints in a web app that are simply secured by an 'Authorize' attribute.)

2020-06-25

This response is AFTER I have already logged in from the Xamarin app -- it occurs when making the API call.

lor-olo commented 4 years ago

Here's what might seem like a dumb question, but what constitutes an 'application' in the context of registering one with Azure AD B2C? I ask this because within the 'Authentication' blade of a registered app, I can add 'Platforms'. So, is my web application and my companion mobile app considered the same application, but with two platforms? Or are they two separate applications?

Separately, how do I enable the Web App/Web API setting for an application without using the "Applications (legacy)" blade?

nickgmicrosoft commented 4 years ago

Hi Iorne, please take a look at this documentation: https://docs.microsoft.com/en-us/azure/active-directory-b2c/add-web-api-application?tabs=app-reg-ga.

We recommend you create one app for your web API and one app for your clients (android, iOS, etc.). For the web API, you set up scopes. For the client app(s), you set up permissions on those scopes. To read more about permissions and scopes, see here.

mmacy commented 4 years ago

@mmacy do we have a doc which clearly shows how to connect a B2C tenant's client app + protected resource (web API), in the Azure AD B2C portal? Maybe @plettb and I can pull from that doc to make things in the ReadMe clearer. Thx.

I recommend performing these in order, at least the app registration/configuration sections in each:

  1. Tutorial: Register a web application in B2C
  2. Tutorial: Enable authentication in a web application using B2C
  3. Tutorial: Grant access to an ASP.NET web API using B2C

The basic flow is:

  1. register application: web app
    1. add a redirect URI
    2. create a client secret
  2. register application: web API
    1. define scopes
  3. back in the web app, grant permissions to the scopes you just defined in the web API

A diagram might look a bit like this:

image

jennyf19 commented 4 years ago

Here's what might seem like a dumb question, but what constitutes an 'application' in the context of registering one with Azure AD B2C? I ask this because within the 'Authentication' blade of a registered app, I can add 'Platforms'. So, is my web application and my companion mobile app considered the same application, but with two platforms? Or are they two separate applications?

Not a dumb question. An application for Azure AD B2C and for AAD is a client, so could be web app, web API, mobile, desktop, etc.. When you create an application, a unique identifier is created for that application in the authorization server (AAD B2C or AAD), which is the client id.

Mobile and desktop applications are public clients, which means they cannot keep a configuration time secret, so they don't have client secrets (you might have noticed this when you create the app).

When you add a platform, you're setting up different redirectUris for each of the platforms you want to support with that one application (client id). Maybe you will run on mobile, but also a web app. So you have to define the redirect uri, which is where the authorization server will return the auth code and the tokens.

For desktop and mobile, we configure the redirect uri for you. You can use the ones which are preconfigured or use your own. image

Separately, how do I enable the Web App/Web API setting for an application without using the "Applications (legacy)" blade?

It's the same as before. Just create a new application and then under Expose an API, you'll add the scope value. image

@lorne-olo

jennyf19 commented 4 years ago

@mmacy thanks!

@lorne-olo this is a mobile app, so if you look at @mmacy 's diagram, you won't have a client secret there.

lor-olo commented 4 years ago

@jennyf19 Thanks for all the info. It's appreciated. I think the final problem I'm having is that I'm trying to use this bearer token with a web app that is already set up to use authorization code grant flow (I think), and now I'm trying to use a JWT bearer token as well. Is it possible to use both authentication schemes in my .NET Core web app? something like...

services.AddAuthentication(AzureADB2CDefaults.AuthenticationScheme)
                .AddAzureADB2C(options => Configuration.Bind("AzureAdB2C", options))
                .AddAzureADB2CBearer("AzureAdB2C", "AzureAdB2C", x => { ... });

Or do I need to create a completely separate Web API app?

jmprieur commented 4 years ago

You can use both, @lorne-olo : but you need to have a different scheme name (you can bind to the same configuration section, though)

onyand commented 3 years ago

I am also experiencing similar issues as above. Mine is a WebApp exposing its own API endpoints with an android front end. The Webapp gets authenticated successfully on B2C. My API scope is well defined as it gets assigned to the scp claim of the id_token on successful logon to android. The access token is also generated on the android application. However, when I attempt to access the exposed API end points decorated with [Authorize] or [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] with the access token generated on android as a bearer token it fails with 401 unauthorized error. I am following the Fabrikamb2c sample that works well against the Microsoft Graph. I think if I can get a peak of the Fabrikamb2c startup setup of authentication and authorization it can help me figure out the issue which is likely to be how the B2C authentication and bearer token schemes have been defined

jmprieur commented 3 years ago

@onyand : for ASP.NET Core web APIs, we recommend you use Microsoft.Identity.Web See https://github.com/AzureAD/microsoft-identity-web/wiki/web-apis

onyand commented 3 years ago

@jmprieur The documentation has not helped in addressing the issue. I still have the 401 error generated for the access token (bearer token) generated on the android application even when used against postman. The code in the documentation is similar to what I have implemented derived from existing Microsoft samples. Seems like what I am missing out is on the correct combination of authentication and authorization setup in the Startup file. I was thinking the Fabrikamb2c startup file for the web app that implements the android scenario in this link would point me in the right direction since it works with Microsoft Graph https://github.com/Azure-Samples/ms-identity-android-java. The code available in this link is the android only, not the web app part

jmprieur commented 3 years ago

@onyand which scope do you request?

onyand commented 3 years ago

@jmprieur I created a scope called "read" against the Expose an API section. Some things to take note are:

  1. On Azure AD B2C, I had to manually modify the manifest as shown below to accommodate both the user and admin consent. For some reason on the GUI interface it only allows creation of admin consent.

  2. In android, the scope has to be specified as a full path (i.e. "https://domain.onmicrosoft.com/clientid/read") for it to work unlike for Microsoft Graph which works by specify the short permission name only (e.g. openid)

  3. The access token generates a scope of "scp": "read" when decoded on jwt.ms

"oauth2Permissions": [ { "adminConsentDescription": "provide read consent", "adminConsentDisplayName": "read", "id": "b9xxxx-xxxx-xxxx-xxxx-baxxxxxxx", "isEnabled": true, "lang": null, "origin": "Application", "type": "User", "userConsentDescription": "provide read consent", "userConsentDisplayName": "read", "value": "read" } ]