Azure b2c .Net core backend is always giving a 401, front react access token front end - asp.net-core

I have a react front-end that sits behind a login with azure b2c, it allows the user to log in if they are registered in my tenant.
I then sent a access token to my backed which i receive using "react-aad-msal" :
signInAuthProvider.getAccessToken({
scopes: ["https://tenantname.onmicrosoft.com/api/scope_name"],
});
When i send this token via a bearer-auth header to my .net core 3.1 back-end i receive a 401.
I am using the addazureadbearer service:
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
and my config section looks liek this:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/tenantname",
"TenantId": "tenantid",
"ClientId": "clientid",
"Audience": "https://tenantname.onmicrosoft.com/api/api.access"
}
i believe it is doing some sort of cross check as i get a 401 not a error being able to connect to azure.

You need to Authenticate with b2c, not with AAD
{
"AzureAdB2C": {
"Instance": "https://<your tenant name>.b2clogin.com",
"ClientId": " your client id",
"Domain": "your tenant domain",
"TenantId": "your tenant id",
"SignUpSignInPolicyId": "your policy name"
}
Please refer to this github on .net core web API in b2c

Related

Authenticate Vue with WebAPI using AzureAD

I am trying to query the data from AzureAD protected WebAPI from Vue using vue-msal.
WebApi: 58ca819e- is webapi app id registered in AzureAD
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "58ca819e-",
"TenantId": "3a0cf09b-"
},
Msal plugin in main.js: be7e77ba is spa client app id registered in AzureAD
import msal from 'vue-msal'
Vue.use(msal, {
auth: {
clientId: 'be7e77ba-x',
tenantId: '3a0cf09b-x',
redirectUri: 'http://localhost:8080/callback'
},
cache: {
cacheLocation: 'localStorage',
},
});
In the component: $msal.acquireToken is called for access token on 2 scopes Microsoft Graph user.read and WebAPI api://58ca819e-/access_as_user where 58ca819e- is WebAPI app id registed in AzureAD.
if (!this.$msal.isAuthenticated()) {
this.$msal.signIn();
}
else{
this.$msal
.acquireToken({scopes: ["user.read", 'api://58ca819e-x/access_as_user']})
.then((res)=>{
console.log(res.accessToken)
this.token = res
})
.catch(err=>console.error(err))
}
The received access token contains only Microsoft Graph but not WebAPI scopes. The token is invalid for the WebAPI.
"scp": "openid profile User.Read email",
When only WebAPI scope is provided, the generated token containing "scp": "access_as_user", can access the WebAPI:
this.$msal.acquireToken({scopes: ['api://58ca819e-x/access_as_user']})
or
this.$msal.acquireToken({scopes: ['api://58ca819e-x/access_as_user','user.read']})
Looks like only 1 scope is used? Can we ask the access token for >1 scopes?
No!
A token can only correspond to one scope. The access token is issued according to the api audience you want to access, and it is unique! A token can only have one audience, and you cannot use multiple scopes to request access tokens.
You should put the api you want to access in the scope. For example, if you want to access MS graph api, you can put https://graph.microsoft.com/.default. If you want to access a custom api, you can put in api://{back-end app client api}/scope name.

ASP.NET Core RBAC mixed with Client Credentials Flow (OIDC)

I have AWS Cognito Identity Provider, a Client (React App) and Resource Server (ASP.NET Core API). React App authenticates a User and gets id token and access token, passing only access token to the API. I want to have RBAC and I'm able to implement it by relying on cognito:groups claim that is part of access_token for all User identities (username or sub is also there). Now I can have
[Authorize(Role="Admin")]
above my ASP.NET Core controller or action and here we go. So far so good.
access token (Authorization Code flow):
{
"sub": "1cefa8a3-0e2a-475b-b7fb-98a725e7d8ef",
"cognito:groups": [
"admins"
],
"token_use": "access",
"scope": "openid profile",
"auth_time": 1605409754,
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_wHXN*",
"exp": 1605413354,
"iat": 1605409754,
"version": 2,
"jti": "458deaeb-3b89-4f57-827a-85efd024f703",
"client_id": "2a232opiiuprvh1aq5pge04d49",
"username": "Google_113965211412849195444"
}
But then I need to implement a scheduled job that uses the API also and has no User in the context of the execution. That is what Client Credentials flow supposed to be used for... But now that it's not a User, but rather a Client (impersonated) the access token is passed to the API have neither username nor cognito:groups claims in it. There is only scope claim that can be used for authorization purposes it seems...
access token (Client Credentials):
{
"sub": "1qpfdgsigoe4f7ii6tq0k4dlfg",
"token_use": "access",
"scope": "my_scope",
"auth_time": 1605437870,
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_wHX*",
"exp": 1605441470,
"iat": 1605437870,
"version": 2,
"jti": "7014d021-13c8-42c0-b9e7-4c80fefcbf2e",
"client_id": "1qpfdgsigoe4f7ii6tq0k4dlfg"
}
So, my questions are:
What's the best practice is this case ? Is it OK to write a custom authorization policy to check for both user-based tokens (and use Roles and probably scope claims) and check for non-User tokens (and rely on scope only)
Is there any example or a guideline for RBAC with Client Credentials?
Thanks

.net core web API not getting authenticated even after generating bearer token from Azure AD using AzureADDefaults.BearerAuthenticationScheme

I am developing a .net core Web API and I am trying to authenticate it using AZURE AD authentication.
I am following below configurations.:
1.In Startup.cs I have added authentication scheme as :AzureADDefaults.BearerAuthenticationScheme
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => { Configuration.Bind("AzureAd", options); });
2.In configure method of startup.cs I have added:
app.UseAuthentication();
3.In app.settings.json I have added following properties:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "<MY client ID>",
"TenantId": "<My Tenant ID>",
"Issuer": "https://login.microsoftonline.com/<My Tenant ID>/v2.0",
"Domain": "<My Domain>",
"ConfigView": "MVC",
"CallbackPath": "/signin-oidc",
"ClientSecret": "<My Client Secret>"
}
I have added Authorize attribute on top of my controller
I have generated my Bearer token using following code:
static void Main(string[] args)
{
Program obj = new Program();
IRestResponse ARMtokenResponse = obj.GetARMAuthToken();
dynamic response = JsonConvert.DeserializeObject(ARMtokenResponse.Content);
Console.WriteLine(response["access_token"].ToString());
Console.ReadKey();
}
private IRestResponse GetARMAuthToken()
{
var client = new RestClient("https://login.microsoftonline.com/<MY TENANT ID>/oauth2/token"); //tenantid
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
request.AddParameter("grant_type", "client_credentials");
request.AddParameter("client_id", "<My Client ID>");
request.AddParameter("client_secret", "<MY CLIENT SECRET>");
request.AddParameter("resource", "https://management.azure.com/");
IRestResponse response = client.Execute(request);
return response;
}
Further I am using this token generated, in postman/console app for calling the API but getting error in response header: Bearer error="invalid_token", error_description="The audience is invalid"
Please help me in this. I am stuck here
There are several issues in your codes :
You should acquire access token for web api , not acquiring access token for Azure Rest API (https://management.azure.com/) , your web api can't validate Azure Rest API's access token .
When acquiring token you are using Azure AD V1.0 endpoint , but when validating token you are using Azure AD V2.0 endpoint (Issuer) .
For Azure AD V1.0 , you can refer to code sample : Call a web API in an ASP.NET Core web app using Azure AD.
For Azure AD V2.0 , you can refer to code samples : Enable your Web Apps to sign-in users and call APIs with the Microsoft identity platform for developers , and follow the 4-WebApp-your-API scenario .

.Net core api with AD B2C OAuth 2.0 - Invalid_token error

I am following this resource. I can get the token successfully but get 401 upon using the token in the second call to my api. It says Bearer error='invalid_token'. Earlier it was giving "Invalid issuer" so I decoded the token to use the issuer in "Instance" field of appSettings.json. Following are appSettings and token. What am I doing wrong?
appSettings.json
{
"AzureAdB2C": {
"Instance": "https://login.microsoftonline.com/xxxxxxxxxxxxxxxxxxxxx/v2.0/",
"ClientId": "452gfsgsdfgsdgssfs5425234",
"Domain": "xxxxxxxxxxxxxxx.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_Auth-SignUpIn"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
token
{
"iss": "https://login.microsoftonline.com/23423fsf234234sfsd42342vsx2542/v2.0/",
"exp": 1551878022,
"nbf": 1551874422,
"aud": "ee965664-d1e3-4144-939a-11f77c523b50",
"oid": "a9ee8ebb-433d-424b-ae24-48c73ae9969c",
"sub": "a9ee8ebb-433d-424b-ae24-48c73ae9969c",
"name": "unknown",
"extension_xxxID": "9f27fd88-7faf-e411-80e6-005056851bfe",
"emails": [
"dfgdfgadfgadfg#dgadg.com"
],
"tfp": "B2C_1_Auth-SignUpIn",
"scp": "user_impersonation",
"azp": "4453gdfgdf53535bddhdh",
"ver": "1.0",
"iat": 1551874422
}
AD B2C instance
Azure AD B2C setting
Postman - revalapi highlighted is the uri of the registered app in the previous shot
Token
Error
Ok. Looks like AD B2C + .Net Core is not happy with onmicrosoft.com URI even though the Microsoft docs resource say it does. See here. I had to use the b2clogin.com uri as shown in below screen shots. Hope it helps someone.
Postman
AppSettings.json
Startup.Auth.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddApplicationInsightsTelemetry();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseMvc();
}
For registering your B2C dotnet core application first You have to login to your B2C Tenant.
After successful Registration configure following step for implicit grant flow.
Reply URLs
Make sure you have done this step accordingly:
Go to Settings and add postman callback URL to : https://www.getpostman.com/oauth2/callback
Once you enter this URL correctly click on Save upper left.
See the screen shot below:
Edit Manifest
For implicit grand flow click on your application manifest and search oauth2AllowImplicitFlow property
make it to true
see the screen shot below:
Your settings for azure B2C is done for implicit grant flow API call.
Postman
Now fire up your post man and select request type as OAuth 2.0 Like below:
Now Click on Get New Access Token and new popup will appear
See the screen shot below:
Add your tenant ID on Auth URL Like this :
https://login.microsoftonline.com/YourB2CTenantId/oauth2/authorize?resource=https://graph.microsoft.com
Set Your Client Id
Set Scope you want to access
Now click on Request Token In response you will get your implicit grant access token:
see the screen shot:
Access Data With this Token:
Copy the token you have accessed already on the Token textbox and select token type as Bearer Token
See the screen shot below:
So tricky part for implicit flow is to set up manifest property oauth2AllowImplicitFlow to true
Hope this could solve your problem. Thank you

Google OAuth2 Service Accounts API Authorization

I'm trying to authenticate my server app through Google's service account authentication but, for some reason, it is just not pushing through.
In the API console, I already created the project, enabled the service I need (Admin SDK), and created a Service Account and Web Application API Access.
When I do use the web application access credentials I am able to authenticate and retrieve user records. But using service account authentication would keep giving me a login required message.
"error": { "errors": [ { "domain": "global", "reason": "required", "message": "Login Required", "locationType": "header", "location": "Authorization" } ], "code": 401, "message": "Login Required" }
I forgot to add, I am testing this with the PHP client library.
public function init() {
$client = new Google_Client();
if (isset($_SESSION['access_token'])) {
$client->setAccessToken($_SESSION['access_token']);
}
$key = file_get_contents(App::SERVICE_KEY_FILE);
$client->setAssertionCredentials(new Google_AssertionCredentials(
App::SERVICE_ACCOUNT_NAME,
App::SERVICE_API_SCOPES,
$key)
);
$client->setClientId(App::SERVICE_CLIENT_ID);
debug($client, 'CLIENT');
if ($client->getAccessToken()) {
$this->access_token = $_SESSION['access_token'] = $client->getAccessToken();
debug($_SESSION['access_token'], 'TOKEN');
} else {
debug('NO TOKEN');
}
$this->client = $client;
}
As you can see, the code is basically about the same as the Google example. Am I missing an extra step?
One last thing, when I authenticate using the web app then access my service account script, the service account script can pick up the web app script's session and push through with the user record retrievals. Does that mean the Admin SDK API explicitly needs user interaction through web app authentication?
Instead of service account, I instead opted to use installed applications API Access.
This ruby gem actually helped my figure this out - https://github.com/evendis/gmail_cli
I was playing with it on the console and just followed the authorization steps in the readme, and found that installed applications is more simple when doing server admin apps.
Being a newb, I think I missed the important part the refresh token plays in the entire process. Going via the installed application approach helped me figure that out.
My config file now contains the client id, client secret, api scope, redirect uri, authorization code, and the refresh token; my initialization code now looks like:
public function init() {
$client = new Google_Client();
$client->setClientId(App::CLIENT_ID);
$client->setClientSecret(App::CLIENT_SECRET);
$client->setScopes(App::API_SCOPES);
$client->setRedirectUri(App::REDIRECT_URI);
if (!$client->getAccessToken()) {
$client->refreshToken(App::REFRESH_TOKEN);
}
$this->access_token = $client->getAccessToken();
$this->client = $client;
}