ASP.NET Zero solution for both identityserver and client - identityserver4

Before getting into the issue, let me tell you what I am trying to achieve.
I need to implement sort of SSO in all of my applications. For which I want to use ASP.NET Zero solutions as
SSO Provider as well as Clients.
Is it possible or am I overthinking?
I am using ASP.NET Zero template: ASP.NET Core - MVC & jQuery
I am very new to IdentityServer and OpenId so please excuse for my silly mistakes if I have made.
In one of ABP project, I have added a static client to IdentityServer AppSettings like below.
First project's AppSettings - Hosted application
{
"ClientId": "localhost",
"ClientName": "MVC Client Demo",
"AllowedGrantTypes": [
"implicit"
],
"RequireConsent": "true",
"ClientSecrets": [
{
"Value": "test"
}
],
"RedirectUris": [
"https://localhost:44302/signin-oidc"
],
"PostLogoutRedirectUris": [
"https://localhost:44302/Account/Login"
],
"AllowedScopes": [
"openid",
"profile",
"email",
"phone",
"default-api"
],
"AllowOfflineAccess": "true"
}
Now from my second ABP project (localhost), I am trying to enable OpenId to authenticated through above server.
Second project's AppSettings - Running on localhost
"OpenId": {
"IsEnabled": "true",
"Authority": "https://[applicationname].azurewebsites.net/",
"ClientId": "localhost",
"ClientSecret": "test",
"ValidateIssuer": "true",
"ClaimsMapping": [
{
"claim": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
"key": "http://schemas.microsoft.com/identity/claims/objectidentifier"
}
]
}
However I am not getting any error, in logs I can see there is a message that says:
AuthenticationScheme: Identity.External signed in.
And a cookie is being created with key "Identity.External" but login-is not happening successfully.
Inside AccountController below line returns null and that resulting into unsuccessful login.
**var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync();**
if (externalLoginInfo == null)
{
Logger.Warn("Could not get information from external login.");
return RedirectToAction(nameof(Login));
}

Try adding
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("sub", ClaimTypes.NameIdentifier);
before services.AddAuthentication()
This will map sub claim to NameIdentifier claim so GetExternalLoginInfoAsync will not return null.

Related

Struggling with optional claims in id/access token

I am updating an internally developed single-page app (Typescript/React) that uses OAuth2 from AD-FS 2016 to Azure AD v2. Things are complicated slightly by the fact that I (the developer) don't have direct access to the Azure console and am working on this with a (non-developer) sysadmin who does.
I have implemented PKCE and got the flow working; I can now obtain JWT access, ID and refresh tokens from the server and authenticate them via JWKS. So far so good.
Now, my apps to know a couple more things:
whether or not the user should be treated as an administrator. This is inferred from group memberships
the preferred username and first name/surname of the user
The first of these we dealt with by setting up a "role" and mapping it out to groups in the Azure console. We then added the role claim to the tokens. I can find this as a string array in "id_token". No problem.
I was confused for a while because I was looking for it in "access_token", but it's not a problem for my app to use "id_token" instead.
The second is the thing that is really giving us problems. No matter what we put into the "optional claims" dialog - we've added all these fields and more, for the ID token, they do not appear in it. Nothing we are doing seems to affect the actual tokens that come out at all.
I am beginning to think that I have missed something out with regards to obtaining the information. I am using the https://graph.microsoft.com/profile, https://graph.microsoft.com/email and https://graph.microsoft.com/user.read scopes and the administrator has authorized these on behalf of the app. The user is synced from our in-house active directory, which the AD-FS is running from as well, so I know that this information is in there. I tried messing with the resource parameter but this is deprecated in Azure AD v2 apparently.
I've read and re-read https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims along with other online documentation, and the following passage confuses me and makes me think that the issue might be related to scopes:
Access tokens are always generated using the manifest of the resource, not the client. So in the request ...scope=https://graph.microsoft.com/user.read... the resource is the Microsoft Graph API. Thus, the access token is created using the Microsoft Graph API manifest, not the client's manifest. Changing the manifest for your application will never cause tokens for the Microsoft Graph API to look different. In order to validate that your accessToken changes are in effect, request a token for your application, not another app.
Or is that just the reason that I switched to using the id_token?
The optional_claims section of the configuration manifest looks like this:
"optionalClaims": {
"idToken": [
{
"name": "email",
"source": null,
"essential": false,
"additionalProperties": []
},
{
"name": "upn",
"source": null,
"essential": false,
"additionalProperties": []
},
{
"name": "groups",
"source": null,
"essential": false,
"additionalProperties": []
},
{
"name": "family_name",
"source": null,
"essential": false,
"additionalProperties": []
},
{
"name": "given_name",
"source": null,
"essential": false,
"additionalProperties": []
},
{
"name": "preferred_username",
"source": null,
"essential": false,
"additionalProperties": []
}
],
"accessToken": [
{
"name": "email",
"source": null,
"essential": false,
"additionalProperties": []
},
{
"name": "groups",
"source": null,
"essential": false,
"additionalProperties": []
},
{
"name": "preferred_username",
"source": null,
"essential": false,
"additionalProperties": []
}
],
"saml2Token": [
{
"name": "groups",
"source": null,
"essential": false,
"additionalProperties": []
}
]
},
But the resulting payload in the ID tag looks like this:
{
"aud": "redacted",
"iss": "https://login.microsoftonline.com/redacted/v2.0",
"iat": 1654770319,
"nbf": 1654770319,
"exp": 1654774219,
"email": "redacted",
"groups": [
"redacted",
"redacted",
"redacted",
"redacted"
],
"rh": "redacted",
"roles": [
"redacted"
],
"sub": "redacted",
"tid": "redacted",
"uti": "redacted",
"ver": "2.0"
}
Can anyone who has more experience of the platform help me understand what we are doing wrong here? Do we need to define custom scopes? Have we simply forgotten to turn an option on?
All help gratefully received! Thanks in advance...
I tried to reproduce the same in my environment and got below results:
I have implemented PKCE flow and got JWT access, ID and refresh tokens.
I added optional claims like below:
Go to Azure Portal -> Azure Active Directory -> App Registrations -> Your App -> Token Configuration
Please check the scopes you are using to get token.
When I gave only openid as scope, got response like below:
But when I gave scope as openid profile email user.read, got all optional claims successfully like below:

Asp.Net Core Api Authorization

I have an Asp.Net Core / ReactJs aopplication. I'm using Microsoft.AspNetCore.ApiAuthorization.IdentityServer to authenticate the API. However, I'm getting an error, which I believe is down to bad configuration.
If I run this locally, with the following config, everything works, and the app redirects to the log-in screen as expected:
"IdentityServer": {
"Clients": {
"MyApp": {
"Profile": "IdentityServerSPA",
}
},
"Key": {
"Type": "File",
"FilePath": "Assets/selfsignedcert.pfx",
"Password": "password"
}
},
However, if I change the config to the following:
"IdentityServer": {
"Clients": {
"MyApp": {
"Profile": "IdentityServerSPA",
"RedirectUri": "https://localhost:5211/authentication/login-callback"
"LogoutUri": "https://localhost:5211/authentication/logout-callback"
}
},
"Key": {
"Type": "File",
"FilePath": "Assets/selfsignedcert.pfx",
"Password": "password"
}
},
It errors (redirecting to the following):
https://localhost:5211/home/error?errorId=1234...
Looking at the auth request, from the client, they are exactly the same; however the second returns an error, while the first successfully redirects.
Is there something wrong with my config? Alternatively, how can I debug this issue?
Both of your Uris in the non-working configuration point to logout. Is this really what you're trying to do?
In the first configuration, you're not defining any Uris, so they will have the following default values:
The redirect_uri defaults to /authentication/login-callback.
The post_logout_redirect_uri defaults to /authentication/logout-callback.
As documented here
Try changing the second configuration to match the default values to see if that helps.
The client configuration of redirect_uri and post_logout_redirect_uri must be identical to the IDP:
redirect_uri: 'https://localhost:5211/authentication/login-callback',
post_logout_redirect_uri: 'https://localhost:5211/authentication/logout-callback',
Or
redirect_uri: $'{IDPhost_config}/authentication/login-callback',
post_logout_redirect_uri: $'{IDPhost_config}/authentication/logout-callback',

Blazor Authentication with Azure AD - Retrieving User Roles

I've successfully setup a Blazor application to authenticate with the Azure tenant where I work. The authentication works beautifully. I have the App Registration setup in Azure with appRoles defined in the manifest. I've add a few users to the application with those roles assigned however I'm not getting any Role claims back on the user context after it authenticates.
Startup.cs
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
Manifest:
"appRoles": [
{
"allowedMemberTypes": [
"User"
],
"description": "Coming soon.",
"displayName": "Viewer",
"id": "{guid goes here}",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Viewer"
},
{
"allowedMemberTypes": [
"User"
],
"description": "Coming soon.",
"displayName": "Manager",
"id": "{guid goes here}",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "Manager"
}
],
I'm trying to retrieve those roles with the claims after authentication, but no roles are coming through. This is obviously making IsInRole not work and for the life of me I can't find any samples of code to achieve this.
I would greatly appreciate it if someone could point me in the right direction!
If the user has been assigned application roles, it should be returned in the id token. You can decode the id token by using https://jwt.io/.
I didn't find a blazor sample, but you can refer to this aspnetcore sample.

How to log out of Google external provider?

Right now I can log out of Identity Server. But when logging back in, I can just select my email address - without having to reenter my password - to log in though Google to access my app.
I want to have to reenter my password (because the device is shared between multiple users). I followed the documentation, but I must be missing something.
(I am using a MVC client to test things out)
Here is the client's configuration:
{
"Enabled": true,
"EnableLocalLogin": false,
"ClientId": "backOffice.mvc",
"ClientName": "BackOffice client",
"ClientSecrets": [
{
"Value": "xxx"
}
],
"AllowedGrantTypes": [
"hybrid"
],
"AllowedScopes": [
"openid",
"offline_access",
"profile"
],
"RedirectUris": [
"http://localhost:5098/signin-oidc"
],
"PostLogoutRedirectUris": [
"http://localhost:5098/"
],
"RequireConsent": false,
"AllowOfflineAccess": true
}
And the provider settings:
.AddOpenIdConnect("Google", "Google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ForwardSignOut = IdentityServerConstants.DefaultCookieAuthenticationScheme;
options.Authority = "https://accounts.google.com/";
options.ClientId = Configuration["GoogleClientId"];
options.CallbackPath = "/signin-google";
options.Scope.Add("email");
})
Thanks a lot for any help! And please let me know if you need more informations :)
Unfortunately Google does not advertise an end_session endpoint via https://accounts.google.com/.well-known/openid-configuration so front-channel sign out is not an option.
However you may be able to provide an additional prompt=login parameter in the authorize endpoint request in an attempt to force interactive authentication. You can enforce this in your client by checking that the auth_time claim is suitably recent.

create a application role using Microsoft Graph API

I have created app registration in azure aad. I want to add a app role using Microsoft Graph API programmatic.
It seems that there is no Microsoft Graph API to do that. If Azure AD graph is acceptable, you use the following rest API to do that.
PATCH https://graph.windows.net/{tenantId}/directoryObjects/{objectId}/Microsoft.DirectoryServices.Application?api-version=1.6
Note: objectId not applicationId, we could get it from Azure portal.
The following is the test body
appRoles": [
{
"allowedMemberTypes": [
"User"
],
"displayName": "SurveyAdmin",
"id": "c20e145e-5459-4a6c-a074-b942bbd4cfe1",
"isEnabled": true,
"description": "Administrators can manage the Surveys in their tenant",
"value": "SurveyAdmin"
}
]
Test result:
We also could check it in the application manifest from Azure portal.
According to my experience, I added appRoles with the help of MicrosoftGraphAPI. You can use this:
respond = "https://graph.microsoft.com/v1.0/applications/{apps-Object-ID}"
request_body = json.dumps({
"appRoles": [ {
"allowedMemberTypes": ['User'],
'description': 'access',
'displayName': 'access',
'id': "XXXX-XXXX-XXX",
"isEnabled": "true",
"value": "null",
"origin": "Application"
}
]
})
response = requests.patch(respond, headers, request_body)
And don't forget to import json and requests

Resources