Hi guys,
So I have got pretty much a base template running with a seperate Identity server endpoint with the angular/core project, not 100% of the advantage if its running from same db but i did it anyway to test it.
Now I am trying to add an external authentication to the identity server so the user can either create a local account or use their AAD account. I have managed to get the login with Active directory button to appear in the identity server endpoint and it looks like it works except when redirected back to my endpoint the user is actually not logged in. Instead it just shows the login screen again. The logs all say successful etc so im not sure if im missing something. I followed doc at https://docs.abp.io/en/abp/latest/How-To/Azure-Active-Directory-Authentication-MVC to get the majority of the settings in but its a little different cause im using the seperate endpoint.
So the following is what I have done
In ProjectNameIdentityServerModule i have
public override void PreConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); PreConfigure<AbpIdentityServerBuilderOptions(builder => { builder.UpdateJwtSecurityTokenHanderDefaultInboundClaimTypeMap = true; //no idea if this is needed but its closest thing i could find to the Jwt mapping instructions in the MVC link });
PreConfigure<IIdentityServerBuilder>(builder => {
builder.Services.AddAuthentication()
.AddAzureAD(options => configuration.Bind("AzureAd", options));
builder.Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => {
//this section is exactly as the mvc how to link is done so i have ommitted it
});
});
}
Then in that same project appSettings.json i have everything per the MVC also but two settings that may affect it i have included below "AzureAd": { "CallbackPath": "/signin-azuread-oidc", "Domain": "my AAD domain" //should this be empty? }
Since everything compiles and it launches the microsoft login as expected and then returns as expected i think all the above is correct configuration i will include the relevant lines from the log in a reply shortly.
19 Answer(s)
-
0
And the relevant log lines
2020-04-17 11:50:03.521 +10:00 [INF] Executing ContentResult with HTTP Response ContentType of application/javascript 2020-04-17 11:50:03.523 +10:00 [INF] Executed action Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationConfigurationScriptController.Get (Volo.Abp.AspNetCore.Mvc) in 453.2645ms 2020-04-17 11:50:03.524 +10:00 [INF] Executed endpoint 'Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations.AbpApplicationConfigurationScriptController.Get (Volo.Abp.AspNetCore.Mvc)' 2020-04-17 11:50:03.524 +10:00 [INF] Request finished in 475.5367ms 200 application/javascript 2020-04-17 11:50:09.044 +10:00 [INF] Request starting HTTP/2.0 POST https://localhost:44315/Account/Login?handler=ExternalLogin application/x-www-form-urlencoded 225 2020-04-17 11:50:09.046 +10:00 [INF] CORS policy execution failed. 2020-04-17 11:50:09.046 +10:00 [INF] Request origin https://localhost:44315 does not have permission to access the resource. 2020-04-17 11:50:09.053 +10:00 [INF] No CORS policy found for the specified request. 2020-04-17 11:50:09.055 +10:00 [INF] Executing endpoint '/Account/Login' 2020-04-17 11:50:09.056 +10:00 [INF] Route matched with {page = "/Account/Login", area = "", action = "", controller = ""}. Executing page /Account/Login 2020-04-17 11:50:09.129 +10:00 [INF] Executing handler method Volo.Abp.Account.Public.Web.Pages.Account.LoginModel.OnPostExternalLogin - ModelState is "Invalid" 2020-04-17 11:50:09.136 +10:00 [INF] Executed handler method OnPostExternalLogin, returned result Microsoft.AspNetCore.Mvc.ChallengeResult. 2020-04-17 11:50:09.138 +10:00 [INF] Executing ChallengeResult with authentication schemes (["AzureAD"]). 2020-04-17 11:50:09.905 +10:00 [INF] AuthenticationScheme: AzureADOpenID was challenged. 2020-04-17 11:50:09.906 +10:00 [INF] Executed page /Account/Login in 849.9002ms 2020-04-17 11:50:09.906 +10:00 [INF] Executed endpoint '/Account/Login' 2020-04-17 11:50:10.018 +10:00 [DBG] Added 0 entity changes to the current audit log 2020-04-17 11:50:10.018 +10:00 [DBG] Added 0 entity changes to the current audit log 2020-04-17 11:50:10.021 +10:00 [INF] Request finished in 976.3547ms 302 2020-04-17 11:50:10.666 +10:00 [INF] Request starting HTTP/2.0 POST https://localhost:44315/signin-azuread-oidc application/x-www-form-urlencoded 2820 2020-04-17 11:50:10.667 +10:00 [INF] CORS policy execution successful. 2020-04-17 11:50:11.580 +10:00 [INF] AuthenticationScheme: Identity.External signed in. 2020-04-17 11:50:11.583 +10:00 [INF] Request finished in 916.6524ms 302 2020-04-17 11:50:11.608 +10:00 [INF] Request starting HTTP/2.0 GET https://localhost:44315/Account/Login?handler=ExternalLoginCallback
2020-04-17 11:50:11.613 +10:00 [INF] Executing endpoint '/Account/Login' 2020-04-17 11:50:11.614 +10:00 [INF] Route matched with {page = "/Account/Login", area = "", action = "", controller = ""}. Executing page /Account/Login 2020-04-17 11:50:11.628 +10:00 [INF] Executing handler method Volo.Abp.Account.Public.Web.Pages.Account.LoginModel.OnGetExternalLoginCallbackAsync - ModelState is "Valid" 2020-04-17 11:50:11.632 +10:00 [WRN] External login info is not available 2020-04-17 11:50:11.636 +10:00 [INF] Executed handler method OnGetExternalLoginCallbackAsync, returned result Microsoft.AspNetCore.Mvc.RedirectToPageResult. 2020-04-17 11:50:11.637 +10:00 [INF] Executing RedirectToPageResult, redirecting to ./Login. 2020-04-17 11:50:11.638 +10:00 [INF] Executed page /Account/Login in 23.2993ms 2020-04-17 11:50:11.638 +10:00 [INF] Executed endpoint '/Account/Login' 2020-04-17 11:50:11.638 +10:00 [INF] Request finished in 30.0089ms 302 2020-04-17 11:50:11.646 +10:00 [INF] Request starting HTTP/2.0 GET https://localhost:44315/Account/Login
2020-04-17 11:50:11.650 +10:00 [INF] Executing endpoint '/Account/Login' 2020-04-17 11:50:11.652 +10:00 [INF] Route matched with {page = "/Account/Login", area = "", action = "", controller = ""}. Executing page /Account/Login 2020-04-17 11:50:11.665 +10:00 [INF] Executing handler method Volo.Abp.Account.Public.Web.Pages.Account.LoginModel.OnGetAsync - ModelState is "Valid" 2020-04-17 11:50:11.668 +10:00 [INF] Executed handler method OnGetAsync, returned result . 2020-04-17 11:50:11.669 +10:00 [INF] Executing an implicit handler method - ModelState is "Valid" 2020-04-17 11:50:11.669 +10:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. 2020-04-17 11:50:11.751 +10:00 [DBG] Added bundle 'Lepton.Global' to the page in 2.60 ms. 2020-04-17 11:50:11.760 +10:00 [DBG] Added bundle 'Lepton.Global' to the page in 3.66 ms. 2020-04-17 11:50:11.761 +10:00 [INF] Executed page /Account/Login in 109.2622ms 2020-04-17 11:50:11.761 +10:00 [INF] Executed endpoint '/Account/Login' -
0
Furthermore using the tip at the bottom of the MVC link I can confirm i am getting a full set of claims back from AAD.
So im starting to think there is a step where a user is created on ABPs side that is missing for whatever reason or i have missed a call to some process to create the user cause there is nothing in any relevant tables related to the AAD user that successfully authenticated.
Any thoughts?
Thanks in advance
-
0
Ok after more moving bits and pieces around I have it working mostly
So for the Identity Server only endpoint you need to put the following in PreConfigureServices
public override void PreConfigureServices(ServiceConfigurationContext context) { var configuration = context.Services.GetConfiguration(); PreConfigure<IIdentityServerBuilder>(builder => { builder.Services.AddAuthentication() .AddAzureAD(options => configuration.Bind("AzureAd", options)); builder.Services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options => { //this events is optional but can be useful for debugging the returned claims options.Events.OnTokenValidated = (async context => { var claims = context.Principal.Claims.ToList(); await Task.CompletedTask; }); options.Authority = options.Authority + "/v2.0/"; options.ClientId = configuration["AzureAd:ClientId"]; options.CallbackPath = configuration["AzureAd:CallbackPath"]; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.RequireHttpsMetadata = false; //you need client secret if your using a private app registration rather than public options.ClientSecret = configuration["AzureAd:ClientSecret"]; options.TokenValidationParameters.ValidateIssuer = false; options.GetClaimsFromUserInfoEndpoint = true; options.SaveTokens = true; options.SignInScheme = IdentityConstants.ExternalScheme; options.Scope.Add("email"); }); }); }
Then in Configure services you need
System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
So mostly what is described in the MVC link but in different locations in the Identity Server only endpoint
I also found some bugs in documentation at https://docs.abp.io/en/abp/latest/how-to/customize-signin-manager
This line
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey"))
Should be
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
Simlarly the line
if (!items.ContainsKey("XsrfKey"))
Should be
if (!items.ContainsKey(XsrfKey))
Note this bug only exists in the documentation, the source it is based on is correct.
Finally my next related problem is that the Identity Server endpoint is now working however the front end for the primary app doesnt show the option to login with the external AAD?
I have no idea how to make it appear as a button yet, I thought because everything was running through the Identity Server endpoint for Auth that it would be reflected, but it looks like its actually an Angular representation that looks identical. So is there a setting somewhere inside the angular project I can turn on so that it sees the same external auth methods of the identity server?
Thanks
-
0
@saintpoida Hello,
This line
if (auth?.Principal == null || items == null || !items.ContainsKey("LoginProviderKey")) Should be
if (auth?.Principal == null || items == null || !items.ContainsKey(LoginProviderKey))
It is not a bug; as it is stated in the docs
..we will be using a minorly modified version of the source code..
Because those are private members as seen here: https://github.com/dotnet/aspnetcore/blob/c56aa320c32ee5429d60647782c91d53ac765865/src/Identity/Core/src/SignInManager.cs#L22-L23
Furhermore, the documentation is for MVC app, angular app will be different and 3rd party openid integration has not been implemented on angular yet.
-
0
@gterdem so that example is meant to fail then?
It is using both a variable and a string representation, if what you said above is true then the following lines are incorrect in the same documentation
var userId = items[XsrfKey] as string;
and
var provider = items[LoginProviderKey] as string;
Is there someway i could proceed with the 3rd party integration into angular? Can we somehow mostly leverage the identity server endpoint from the angular app, by that i mean actually direct the not authenticated user to identity server endpoint instead of the angular login page, then back again when they log in?
-
0
@saintpoida thanks for point that out, we will fix the documentation.
Currently, you can define a new client for your angular app in the identityserver with implicit flow and alter the authentication of angular app. This link may help: https://damienbod.com/2016/03/02/angular2-openid-connect-implicit-flow-with-identityserver4/
-
0
Hi @gterdem
Ok so I have managed to mostly get what i want working, after much reading and playing around with different solutions i now have a different .net application, a blank angular project, abp io mvc and abp io angular all authenticating with 1 identity server. The angular edition is doing a full redirect to the identity server endpoint login page so that i can have external providers all work from one place.
Thankfully the npm package you guys are using for oidc already works with the implicit and PKCE. However i have a few questions related to this
To use PKCE with angular then in the identity server client setup i have to put RequirePKCE true and RequireClientSecret false, however i cant find anyway to do this through ABP Identity Server UI? I have done it direct in db but am wondering if there is some place in the UI i have missed that I could set these 2 options?
Also exceptions thrown in the Identity Server endpoint just seem to show a blank page and I have to go to the logs.txt file of the Identity server to see the issue, things that i think should show a nice error to the user like their password being in the incorrect format? Is it supposed to do that or is that a bug?
Sorry also forgot to add I want to replace the route /account/login in angular whats the best way to do this, I have done it with replaceable component however it still shows the page with tenant switch with my own content in the middle (where username password fields used to be)? I would prefer to replace the whole route without recompiling all the abp npm packages and source?
The same goes for the AuthGuard, is there an easy/correct way i can inject another version so im not breaking the source code?
Thanks again Pete
-
0
hi @saintpoida,
Can you show where you set
RequirePKCE
andRequireClientSecret
values in the official Identity Server 4 Admin UI? -
0
hi @alper
No I didnt set them using the official admin UI, i did it direct in the database. I have not looked at the official UI only the ABP IO implementation?
I will have a look at the official implementation and see if i can find them
-
0
hi @alper
This documentation found at this link shows a screenshot with PKCE option, i will keep looking for RequireClientSecret
Im not sure how this helps unless ABP IO is built on top of that UI? Im happy to try and add the UI for it to ABP IO if it doesnt exist I am simply wondering if it is on a screen I have missed
-
0
Hi @alper
After further investigation I can see the properties for both PKCE and RequireClientSecrets in the src code for the ABP identityserver domain object as well as the ETO object.
https://github.com/abpframework/abp/tree/dev/modules/identityserver
but i dont have the license to the UI modules to see if they are in there, maybe you can confirm? To reiterate I am simply wondering if the options exist and I cant find them or if they have not been added to the UI yet?
-
0
@saintpoida
You are right. Those fields must be in UI and they will be there next release.
-
0
@yekalkan
Ok cool!
So now my last 2 questions really
- Exceptions thrown in the Identity Server endpoint (at least when its a separate endpoint, i have not tested a combined solution yet) seem to just be blank screens, im not sure if that is a bug or if its expected behaviour?
- Is there a way I can overwrite the account/login route in angular or inject a different authguard so that I can overwrite the path it goes to when someone not logged in? Right now I have managed to inject components to take place of the LoginComponent and TenantBoxComponent so that when they are redirect to the account/login page it just shows a please wait message but I am interested if there is a better way to do that? Or if I could inject a component to take over the Layout component of account/login page and make it even cleaner
Thanks again
-
0
hi @saintpoida,
1- you can file an issue on https://github.com/abpframework/abp/issues with the repro steps. we'll check if it's a bug or not. 2- @mehmet can you answer for this ?
-
0
@saintpoida hello,
Exceptions thrown in the Identity Server endpoint (at least when its a separate endpoint, i have not tested a combined solution yet) seem to just be blank screens, im not sure if that is a bug or if its expected behaviour?
If you mean you can only check the error via logs but can't see a friendly error on UI about backend traffic, it is default behavior of identityserver logging. I am guessing it's high probably because of security concerns.
-
0
@gterdem hi
What would be the best practise then to catch all the exceptions so I can wrap the relevant ones into User Friendly ones such as the password format when registering being incorrect. e.g if a user registers a new account with password of 'a' then thats invalid and they just get a blank screen on hitting register rather than a validation error.
@alper thanks I will get a new solution and just confirm it is indeed reproducible and will log a bug if it is. I will also try a combined solution and see if its any different
-
0
Is there a way I can overwrite the account/login route in angular or inject a different authguard so that I can overwrite the path it goes to when someone not logged in? Right now I have managed to inject components to take place of the LoginComponent and TenantBoxComponent so that when they are redirect to the account/login page it just shows a please wait message but I am interested if there is a better way to do that? Or if I could inject a component to take over the Layout component of account/login page and make it even cleaner
Hi @saintpoida
Here is the Account module's components:
| Component key | Description | |----------------------------------------------------|-------------------------| | Account.LoginComponent | Login page | | Account.RegisterComponent | Register page | | Account.ManageProfileComponent | Manage profile page | | Account.ForgotPasswordComponent | Forgot password page | | Account.ResetPasswordComponent | Reset password page | | Account.AccountComponent | The component that wraps register, login, forgot password, and reset password pages. Contains
<router-outlet>
. | | Account.ChangePasswordComponent | Change password form in manage profile page | | Account.PersonalSettingsComponent | Personal settings form in manage profile page | | Account.TenantBoxComponent | Tenant changing box in account component |Did you try to replace
AccountComponent
? I think you can do whatever you want by replacing this component.You can define your custom guard to AccountModule route definition in AppRoutingModule like below:
{ path: 'account', canActivate: [MyAuthGuard], loadChildren: () => import('@volo/abp.ng.account').then(m => m.AccountModule), },
Or you can override the AuthGuard completely by adding a provide to AppModule like below:
// app.module metadata providers: [{ provide: AuthGuard, useClass: MyAuthGuard }]
-
0
@saintpoida,
What would be the best practise then to catch all the exceptions so I can wrap the relevant ones into User Friendly ones such as the password format when registering being incorrect. e.g if a user registers a new account with password of 'a' then thats invalid and they just get a blank screen on hitting register rather than a validation error.
Notice that user registration validation is related with Identity management, not IdentityServer. Hence it is not related with IdentityServer logging or exception handling. If user is trying to register with an invalid character and getting a blank screen instead of a validation error; you are right. There should be a user friendly exception to notice the user about that. Would you mind to create an issue about the problem with detailed reproduce steps on github?
-
0
@Mehmet thanks did not realise i could replace whole account component, although i dont know if i still need forgot password and other pages other than login so i will try authguard injection first!
@gterdem ok great i will try reproduce in new solution and then log an issue if thats the case
Thanks again for all the help