Azure ADB2C multiple Web APIs authentication - authentication

My scenario is related to the authentication of two or more web APIs from the same MVC Web App in which ADB2C is configured.
I have created two web apis in Azure ADB2C and granted permissions of both the web apis into ADB2C MVC web app. However whenever I tried to obtain the access token, it is giving me the access token for one web api but not giving the access token for the second one.
I want to know whether this scenario is possible in ADB2C or not?
Thanks.

One access token could be used to access by one resource(web api) . If you want another resource's access token , you can use refresh token(if exists) to get the new token :
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#refresh-the-access-token
If using MSAL.NET ,after initial token acquisition , you can invoke AcquireTokenSilent, asking for the api scopes you need :
// Retrieve the token with the specified scopes
var scope = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
IConfidentialClientApplication cca =
ConfidentialClientApplicationBuilder.Create(AzureAdB2COptions.ClientId)
.WithRedirectUri(AzureAdB2COptions.RedirectUri)
.WithClientSecret(AzureAdB2COptions.ClientSecret)
.WithB2CAuthority(AzureAdB2COptions.Authority)
.Build();
new MSALStaticCache(signedInUserID, this.HttpContext).EnablePersistence(cca.UserTokenCache);
var accounts = await cca.GetAccountsAsync();
AuthenticationResult result = await cca.AcquireTokenSilent(scope, accounts.FirstOrDefault())
.ExecuteAsync();
MSAL will look up the cache and return any cached token which match with the requirement. If such access tokens are expired or no suitable access tokens are present, but there is an associated refresh token, MSAL will automatically use that to get a new access token and return it transparently.
You can click here for code sample .

Related

Azure AD B2C Service to service authentication

I need service to service authentication.
I followed this documentation Azure AD B2C s2s
and everything looks good so far except for I am not able to acquire access token for client service in any other way rather than just explicitly calling for POST authentication endpoint.
So I am able to retrieve access token doing in code something like this:
var bodyContent = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", "xxx"),
new KeyValuePair<string, string>("client_secret", "xxx"),
new KeyValuePair<string, string>("scope", "https://xxx/.default")
};
var result = await _httpClient.PostAsync("auth_url", new FormUrlEncodedContent(bodyContent));
var accessToken = await result.Content.ReadFromJsonAsync<AccessTokenResponse>()
What I was trying to achieve is trying make it work automatically in some way using:
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration, Constants.AzureAdB2C)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { builder.Configuration["MyScope"] })
.AddInMemoryTokenCaches();
and then acquire token like this:
string result = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
instead of doing it manually like in the example above.
But whatever I am trying to do I still get No account or login hint was passed to the call
Update 02/11/2022
Seems like you're simply using the wrong method to request an authorization code
When you want a (daemon or app) service to authenticate to another service:
Don't use GetAccessTokenForUserAsync(); // Wrong, it uses the current logged in user for the request
Do use GetAccessTokenForAppAsync() // Correct, it request a code via client_credentials flow
Short answer: You're passing in scopes to .EnableTokenAcquisitionToCallDownstreamApi() instead of the AzureAD OpenIdConnect configuration with a separate app registration attached to it.
The long answer:
"The OAuth 2.0 client credentials grant flow permits an app (confidential client) to use its own credentials, instead of impersonating a user, to authenticate when calling web resource, such as REST API." [Microsoft Docs]
This flow does not authorize with a user who initiated the request, it uses its own credentials.
Therefore, you need 3 app registrations in AzureAD B2C.
[1] The front-end app registration where your users sign in
[2] The app registration you will use to generate authorization codes and
authorize scopes
[3] The app registration for the service you're trying to call.
With that in place you add a new section to your AppSettings.json in the front-end (eg. name it "downstreamApi"), containing the client secret, scopes, and some default OIDC settings like Authority, and then you can then simply call
services
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.EnableTokenAquisitionToCallDownstreamApi()
.AddDownStreamWebApi("MyAPI", Configuration.GetSection("downStreamApi");

I want to use MsGraph in B2C, but I get a corrs error when getting an access token

I have an SPA that uses B2C as its certification.
not AD, im using B2C.
Now I want to display user information on the SPA,
so I want to use MsGraphAPI to get data from B2C.
Therefore, we are trying to obtain an access token using credential flow.
The code is as follows
requestUri = "https://login.microsoftonline.com/{tenandId}/oauth2/v2.0/token";
var params = new URLSearchParams();
params.append("grant_type","client_credentials");
params.append("client_id","XXXX");
params.append("client_secret","ZZZZ");
params.append("scope","https://graph.microsoft.com/.default");
const response = fetch(requestUri, {
method:"POST",
body:params
});
At this time, the developer tool shows a corrs error.
The following error.
access to fetch "https://login.microsoftonline.com/{tenandId}/oauth2/v2.0/token" from origin "http://localhost:3000" has been blocked by CORS policy.
How can I resolve this?
SPA is available at http://localhost:3000.
Is the localhost no good?
The purpose of authenticating is to get user information. You can insert any attribute into the token, and then parse the id_token to display the users profile information in your application. Calling MS Graph API for this seems counter intuitive.
Next, you shouldnt be using client_credentials in your SPA, those are secret credentails, and should only be used by your server. By exposing those credentials in your SPA, any user could extract them and use them to read your entire directroy store. This is why you are getting a CORS error, it's never supposed to be running on the client browser.

Identity Server 4, External providers and Web API

I’m in process of developing system which consists from such parts:
- Some services under gateway (Ocelot)
- Mobile client (iOS)
- Identity Server 4
Mobile client hasn’t been prepared yet, so I use Postman for emulating requests from it. My problem is implementation of Authentication with External providers, like Google. It’s my first experience of using IS 4, so I have some misunderstanding and difficulties. Excuse me, if my question is too abstract or if I miss smth obvious.
I successfully deployed IS 4 using all this tutorials and it works with Password Credentials flow in a proper way: I request IS for access token, sending user credentials, it returns token and I can successfully use it for access to my API methods.
Situation with External Providers are different. I’ve overviewed this tutorial (https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/google-logins?view=aspnetcore-3.1) and some other and add code from it to the IS project. I can successfully log in with Google, using a button on that IS4 web-page which goes with IS 4 Quickstart UI template. But no chance to work with API. As I understand in such workflow client-app should go for a token not to my IS as in example with a local user, but to the Google Auth provider. And I emulated it with Postman and got a strange access_token which has no data and it_token which contains username, email and so on. I try to use this id_token with requests to my API. The result is always 401.
Where I’m wrong? How should I build requests to API with token from Google? Or I have misunderstanding and there should be another flow: client goes to IS with specific request, IS goes to Google and then returns proper token to Client?
Here is configuration of authecation on the side of Web API app:
private void ConfigAuthentication(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "k_smart_api";
});
}
Here is config of Google-Auth on the side of IdentityServer:
services.AddAuthentication().AddGoogle(opts => {
opts.ClientId = "My google client Id";
opts.ClientSecret = "my google client secret";
opts.SignInScheme = IdentityConstants.ExternalScheme;
opts.SaveTokens = true;
});
This is how I get Access Token:
postman exampple
The tokens you get back from Google, is only used to Authenticate the user in Identity Server. Then after Identity Server receives those tokens, it sign-in the user and create new tokens (ID+access) that are passed to your client. you should look at using the authorization code flow in your client to authenticate the user and to get the tokens. then use the access token received to access your API.
do remember that the tokens received from Google are not used to give access to your APIs.

Get UserInfo from ADFS in UWP with ADAL

I am trying to authenticate the user with ADFS and I am using ADAL. Authetication seems to work since I can get the AccessToken. The problem is that looking at the code authResult contains a UserInfo where all properties (for instance GivenName or FamilyName) are null.
AuthenticationContext authContext = null;
AuthenticationResult authResult;
try
{
authContext = new AuthenticationContext(authority, false);
authResult = await authContext.AcquireTokenAsync(resource, clientId, new Uri(returnUri),
new PlatformParameters(PromptBehavior.Auto, false));
}
Those values are null because of ADFS configuration? I noted that decoding the AccessToken returned I can read User information. But I don't think that decoding the JWT Token is the right way to achieve those information. Do you have a better suggestion?
I have also seen people getting information by using claims, but I don't know exactly how to use it on UWP, since all the sample I found used
ClaimsPrincipal claimsPrincipal = System.Threading.Thread.CurrentPrincipal as ClaimsPrincipal;
But System.Threading.Thread is not available on UWP.
Normally, the access_token is used in Oauth and OpenID connect scenarios and intended to be consumed by the resource. To identify the user we should use the id_token( verify the token and extract the claims abut user by decoding the token). Please refer below about the usage of tokens:
id_token: A JWT token used to represent the identity of the user. The
'aud' or audience claim of the id_token matches the client ID of the
native or server application.
access_token: A JWT token used in Oauth and OpenID connect scenarios
and intended to be consumed by the resource. The 'aud' or audience
claim of this token must match the identifier of the resource or Web
API.
refresh_token: This token is submitted in place of collecting user
credentials to provide a single sign on experience. This token is
both issued and consumed by AD FS, and is not readable by clients
or resources.
And you can refer the link below about the native client to web API scenario for ADFS:
AD FS Scenarios for Developers - Native client to Web API
Depending on the ADFS version of your server. If your company is using Windows Server 2012 R2, then it is ADFS 3.0. I did successfully integrate with SSO login created by the admin of company I am working in. You should refer to this article before venturing in : https://learn.microsoft.com/en-us/previous-versions/adfs-windows-server-2012r2/dn660968(v=msdn.10). Note : you don't even need to make a web api of ToDoList.
using only GetAuthorizationHeader() and authenticationContext.AcquireTokenAsync(), you could obtain the token by asking the user to authorize their credentials and decrypt the receive token.
This is sample of code I did:
authority = https://contoso.com/adfs/ls (Endpoint from the ADFS metadata)
resourceURI = https://localhost:44300/ (Relying party, ask your ADFS admin to register)
clientID = it is recommended to use Package.appmanifest's package name from Packaging tab. As long as it is a unique ID.
clientReturnURI = use the following code to obtain the clientReturnURI (also available in the article in the link) :
string clientReturnURI = string.Format("ms-appx-web://Microsoft.AAD.BrokerPlugIn/{0}",WebAuthenticationBroker.GetCurrentApplicationCallbackUri().Host.ToUpper());
AuthenticationContext ac = new AuthenticationContext(Authority_Uri, false);
AuthenticationResult ar = await ac.AcquireTokenAsync(resourceURI, GlobalVar.clientID, new Uri(clientReturnURI), new PlatformParameters(PromptBehavior.Always, true));
var jwt = new JwtSecurityToken(ar.AccessToken);
string unique_name = jwt.Claims.First(c => c.Type == JwtRegisteredClaimNames.UniqueName).Value;
You can replace JwtRegisteredClaimNames.UniqueName with anything else. It depends what info/claims that is available in the access token. You should inspect the available info in the jwt by placing breakpoint at var jwt. Or you can decrypt the access token in the AuthenticationResult.AccessToken in this website :
https://jwt.ms/
Lastly, you need to install certificate from your ADFS admin and install the certificate across your web and UWP server to allow the application able to trust execute the actions.

Get Access token with Azure AD multi-tenant openID authentication

I followed the sample code here to create a MVC web app with Azure AD multi-tenant OpenID authentication. I use the following code to get user sign in.
public void SignIn()
{
if (!Request.IsAuthenticated)
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
}
Now I need to send a web api call which is protected by my Azure AD as well. Currently, before I send the request I use ADAL library to ask users to login again and get the access token like this.
AuthenticationContext ac = new AuthenticationContext(authority);
AuthenticationResult ar = ac.AcquireToken(resourceID, clientID, redirectURI, PromptBehavior.Always);
string accessToken = ar.AccessToken;
However, since the authentication used in the MVC(if the user is from my AD) is the same as the one used to protect the web api. I'm wondering if there is a way to get the access token when user login with this openID authentication so that I can skip the second login with ADAL?
UPDATE:
Following vibronet's answer, I am trying to use the following code to get the token:
string authority = "https://login.windows.net/ucdavisprojecthotmail.onmicrosoft.com";
ClientCredential clientcred = new ClientCredential(clientId, appKey);
AuthenticationContext authContext = new AuthenticationContext(authority);
AuthenticationResult result = authContext.AcquireTokenSilent(resourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
Here, this code is used in an MVC web app and the clienId and appKey is the clientID and key of the web API I want to call. The resoureID is the APP ID URI of the web API obtained in Azure portal.
However, I got this error: Failed to acquire token silently. Call method AcquireToken. Anything I was missing?
Absolutely. Check out https://github.com/AzureADSamples/WebApp-WebAPI-MultiTenant-OpenIdConnect-DotNet, it's like the sample you've been working with but with in addition the access token acquisition and use you are asking about. Also note, AcquireTokenSilent can only work if you have a token in the cache - to be used directly or refreshed. FInally: when you ask for a token, you must specify both the ID fo the resource you want a token for, and the clientID of the application doing the request. In your code, you appear to have used the clientID of the target app. Please refer to the sample I linked above, it shows the exact pattern to be used in this scenario.