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

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

Related

ASP.NET Web API authorized using Azure AD: Authorization has been denied

I secured some of my ASP.NET Web API using Azure AD as you can see in the code screenshot below:
The strange thing is that sometimes when calling the API in parallel (bulk calls) the client gets an error:
Authorization has been denied for this request
I am unable to detect why it happens only sometimes even if the client re-tries the call with another access token. Is there a way to find/debug the exact reason why? It is maybe because I need to resize the Web/DB(DTU) servers?
Please try to Enable PII logging in the startup.cs file in configure services method to check for the proper error.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
// Enable PII logging
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
...
}
This error: Authorization has been denied for this request usually occurs when there is audience mismatch or when the audience doesn’t match the one web api is expecting which is set in ValidAudience . The audience can be appid or appIdUri according to the application.
So in place validAudience, please use tokevalidationparameters.validaudiences or ValidAudiences to add both the clientID and the AppIdURI (ap://<appIdUri>) in place of AUDIENCE1 and AUDIENCE2
ValidateIssuer = true,
ValidAudiences = new List<string>
{
"AUDIENCE1",
"AUDIENCE2"
}
with such configuration, the api call can be validated for both the cases.

Custom API route will respond with Forbidden if JWT token generated with Skoruba (Identity Server 4) is used

We've started implementing a new Web API with ASP.NET Core 2.2 and it has been decided that it should use Identity Server 4 for authentication/authorization duties. Furthermore, its Skoruba implementation has been chosen as it looks like it should fulfill most, if not all our needs in that regard.
We got the identity server and Skoruba up and running, but when it comes to consuming the JWT token in our own API, even assigning just one role to the test user, we'll keep hitting the same brick wall. The following request to the Skoruba API will respond with a 200:
http://localhost:5000/connect/token:
It successfully returns a JSON string with access_token, expires_in and token_type ("Bearer").
After that, a request to the http://localhost:5000/connect/userinfo route of the API with the following header
will also respond with a 200 and return the following JSON string:
{
"sub": "aeccf460-7d0d-41ae-8b52-a051138f5c05",
"role": "Administrator",
"preferred_username": "dev",
"name": "dev"
}
Please take notice that "role": "Administrator" assigned to user dev is something I set up myself using the Skoruba Admin UI, so that JSON is pretty much what I wanted. So for all intended purposes it looks like I have the information I need right now. I just can't consume it. If I try to retrieve the JWT token in our own back end, I am successful (this is obviously just for testing purposes):
[HttpGet("GetAccessToken")]
[AllowAnonymous]
public string GetAccessToken()
{
var accessToken = HttpContext.Request.Headers["Authorization"];
var token = accessToken.First().Remove(0, "Bearer ".Length);
return token;
}
With all that said, onto the actual problem: calls to a route that demands authorization in our API are treated in the same fashion as calls to Skoruba's userinfo action (particularly, the headers):
Inside this same Controller ("Foo"), I implemented a simple Get method, which should only be accessed with the correct role, which I assume is information fetched from HttpContext.Request.Headers["Authorization"] and hoped the framework would use it accordingly:
[HttpGet]
[Authorize(Roles = "Administrator")]
public IActionResult Get()
{
try
{
var response = ConvertListToJsonResponse(GetAll()); //Don't mind me
return Ok(response);
}
//...
}
At this point, my API server responds with the infamous 403 Forbidden status code. Not sure where to go from here and research proved unwieldy so far. Any help is appreciated, I'll provide more code and info if necessary.
EDIT
This is the generated token:
eyJhbGciOiJSUzI1NiIsImtpZCI6IjA4NTMzNmFmZTY0Yzg2ZWQ3NDU5YzE5YzQ4ZjQzNzI3IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1Njg3NDU5NTgsImV4cCI6MTU2ODc0OTU1OCwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwL3Jlc291cmNlcyIsImNsaWVudF9pZCI6ImF1dGhfdGVzdCIsImNsaWVudF9hY2Nlc3NfcmlnaHRzIjpbImxpc3QiLCJhcHByb3ZlIiwia2VlcCJdLCJzdWIiOiJhZWNjZjQ2MC03ZDBkLTQxYWUtOGI1Mi1hMDUxMTM4ZjVjMDUiLCJhdXRoX3RpbWUiOjE1Njg3NDU5NTgsImlkcCI6ImxvY2FsIiwic2NvcGUiOlsib3BlbmlkIiwicHJvZmlsZSIsInJvbGVzIl0sImFtciI6WyJwd2QiXX0.Ihsi6W5ukGcZ4Chkuk5XMaoqTkUR_1hBQlIcdHtMWiBA-EyAIncX5STCng_6ZPgN89Np6Y_hemFFyVtHEdN_vP6i0HuaXgznzrnJ4zq4Iiz9jxpZqpSSE9cXpSG8hPOZe5kGfD2J6_GPxnraGH_1ZF94AhmlspIvqFAAQrQ-0c7-dCduP4ledkQvBKz-rXszGp35W7Gb5nvpcVt4oe67mqETdwtgGengk2eCwHeKdA94EYnj_HErPNTjJhh5k75fDQ0IiOS-xHRK8BQmLhRh_UZwB3H5qZymFJNr_yb-ljFqIeEHptSWLBO1XrKYs1BqB9KwxIROKqmxeNGTnpCUSQ
The resulting payload:
{
"nbf": 1568745958,
"exp": 1568749558,
"iss": "http://localhost:5000",
"aud": "http://localhost:5000/resources",
"client_id": "auth_test",
"client_access_rights": [
"list",
"approve",
"keep"
],
"sub": "aeccf460-7d0d-41ae-8b52-a051138f5c05",
"auth_time": 1568745958,
"idp": "local",
"scope": [
"openid",
"profile",
"roles"
],
"amr": [
"pwd"
]
}
I see the claims I added to the client, but what I really need at the moment is simple Authentication, which I suppose should be provided by a role. Or am I completely off?
This question, and more specifically, this answer helped me to understand what was going on and map Skoruba's UI functionalities to IdentityServer4 inner workings. Credits goes to Ruard van Elburg.

Azure AD User info with JWT Bearer token and ASP.NET Core 2 WebApi

I found a tutorial where I can sign in to my application with Azure AD credentials.
In my frontend I'm using Xamarin.Forms.
In my backend I'm using ASP.NET Core 2.0 WebApi.
Backend:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseAuthentication();
app.UseMvc();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Authority = String.Format(Configuration["AzureAd:AadInstance"], Configuration["AzureAD:Tenant"]);
options.Audience = Configuration["AzureAd:Audience"];
});
}
It's pretty simple.
In my frontend I'm filling in my credentials and asking for a access_token.
{
"token_type": "Bearer",
"scope": "user_impersonation",
"expires_in": "3600",
"ext_expires_in": "0",
"expires_on": "1507104075",
"not_before": "1507100175",
"resource": "my_resource",
"access_token": "my_access_token",
"refresh_token": "my_refresh_token"
}
The access_token i'm filling in the headers with Authorization set with bearer my_access_token.
My Api know's all my information because it will automaticly set claims with the info from my access_token. This info is provided by Azure AD. (fullname, firstname, lastname, ...)
But how can I get this information in my frontend?
You might want to check out the active-directory-dotnet-native-desktop sample on GitHub.
I shows how to use ADAL.NET in a desktop app, to get a token for a service. you will need to adapt it for your Xamarin forms client, but the principle is the same as far as authentication is concerned.
Also it contains a service and you would replace it by your own service and get a token for your web API by changing the resource ID to be the one of your application created using the ASP.NET wizard (you'll find it in the Azure portal as described in the readme.md of the sample)
the idea is that you first get a token using ADAL.Net line 92 of TodoListClient/MainWindow.xaml.cs
result = await authContext.AcquireTokenAsync(todoListResourceId, clientId, redirectUri, ...)
and then you use it as a bearer token line 121
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
If all the info you required is include in the access token, you can just decode the access token on the client. The access token is a JWT, it is easy to research code sample to decode the access token like following threads:
How to decode JWT Token?
Decoding and verifying JWT token using System.IdentityModel.Tokens.Jwt
And if you also require more user info, you can refresh the access token for the Microsoft Graph, and call the me endpoint of Microsoft Graph(refer here). And below is the document about how to refresh the access token via refresh token:
Refreshing the access tokens

Google authentication with MVC6

Following the instructions in the documentation I set up Facebook authentication like so:
"dependencies": {
//blah blah snip
"Microsoft.AspNetCore.Authentication.Facebook": "1.0.0-rc2-final"
},
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//blah blah snip
app.UseIdentity();
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseFacebookAuthentication(new FacebookOptions()
{
AppId = Configuration["Authentication:Facebook:AppId"],
AppSecret = Configuration["Authentication:Facebook:AppSecret"],
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
After procuring AppId and AppSecret from Facebook and storing them using SecretManager, I fired it up, whereupon there was a successful Facebook login and much rejoicing.
Emboldened by success, I tried to set up Google authentication along the same lines.
"dependencies": {
//blah blah snip
"Microsoft.AspNetCore.Authentication.Facebook": "1.0.0-rc2-final",
"Microsoft.AspNetCore.Authentication.Google": "1.0.0-rc2-final"
},
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//blah blah snip
app.UseIdentity();
// Add external authentication middleware below. To configure them please see http://go.microsoft.com/fwlink/?LinkID=532715
app.UseFacebookAuthentication(new FacebookOptions()
{
AppId = Configuration["Authentication:Facebook:AppId"],
AppSecret = Configuration["Authentication:Facebook:AppSecret"],
});
app.UseGoogleAuthentication(new GoogleOptions()
{
ClientId = Configuration["Authentication:Google:ClientId"],
ClientSecret = Configuration["Authentication:Google:ClientSecret"],
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
It almost works but The redirect URI in the request, https://localhost:44391/signin-google, does not match the ones authorized for the OAuth client.
A little research revealed that the redirect URL is supplied by the value of the CallbackPath property of the GoogleOptions object, and it defaults to "signin-google". I don't have a signin-google path and CallbackPath = "/" produces an unhandled remote failure (whatever that is) during app initialisation so I removed it and updated the app details with Google to allow https://localhost:44391/signin-google as a redirect path.
This got me prompted to grant the app "Have offline access", so I did.
But now I get 403 Forbidden with a URL like this https://localhost:44391/signin-google?state=CfDJ8MbSjmyKVXJDoa23x_EQBp75rji1GdkCSc5HU6PFA1Ta534Ag-bZ3EtTcDpLaD5otpLXXQnqmPHRaUFbD2izzBoY_HM9vjJnVYcJNtRmkALdGQvMkcBc9J56UsJU9-uyYIGbsMMfgWhdkVry7Ssc76hJMwvjtDSFohWoqcGa7CxkFfrwfailX5QDmdfdFEtjy-txpt2c064lBpsjHUq4HeU9qIlMwq4T8yDtEsklATbkjlrVu-UvLMuTadraxVdd1s1m3NPe0LWFSkfukdhNoeEyBmkEgu3JBX4sevP3Yc29_zfojOglVxDFHTCtqDB-Tg&code=4/lFOCSBncHEH0RArYZrJ7Bd2p1URIzWxLTvJikXy1S1U&authuser=0&hd=assaohs.com.au&session_state=8b968ac03c755e48f2c1479fea9c02b9ccaa292a..4748&prompt=none#
I tried setting scope like this
var go = new GoogleOptions()
{
ClientId = Configuration["Authentication:Google:ClientId"],
ClientSecret = Configuration["Authentication:Google:ClientSecret"],
};
go.Scope.Add("email");
go.Scope.Add("profile");
app.UseGoogleAuthentication(go);
Interestingly the default scope is "openid". Adding "email" and "profile" scopes didn't produce any difference in the grant request. Adding invalid scopes produced an error, implying that these are valid recognised scopes.
Using the Google developer console to enable the Google+ API for the app resolves the 403 Forbidden error, but for reasons that escape me we are still prompted to grant permission for offline access.
The GoogleOptions object has a property AccessType which defaults to null. Setting it to "online" does not seem to make any different.
Questions
I am prompted to grant offline access. What makes the server think I want offline access? What needs to be set to prevent this? Setting AccessType = "online" in the GoogleOptions object initialiser
var go = new GoogleOptions()
{
AccessType = "online",
ClientId = Configuration["Authentication:Google:ClientId"],
ClientSecret = Configuration["Authentication:Google:ClientSecret"],
};
doesn't seem to have any effect. Is this the right value?
Offline access is the default message for a bunch of the different scopes email and profile are two of them. There is no way to change the message short of stopping to request profile and email scopes.
AccessType (online,Offline) isn't really what you think. Offline access will return a refresh token so you can access their data at a later time. Online access means that you are only going to be accessing their data when they are there and you don't need a refresh token.

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;
}