My client get token from IdentityServer4 and give it to my API to get service,
all thing goes true until i want to get user claim (which STS automatically put on the user claim), when i call User.Identity.Name, it return null.
Accroding to the This Link i should define Scope in the API startup, so i define:
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:33934",
RequireHttpsMetadata = false,
ApiName = "api1",
AllowedScopes = { "profile", "email", "api1" },
});
All thing goes right, my user can get Token from IdentityServer, and when i pass it to API, the Authorization will done successfully, but the User.Identity.Name return null!
what is the problem?
You can print out the list of claims using
<dl>
#foreach (var claim in User.Claims)
{
<dt>#claim.Type</dt>
<dd>#claim.Value</dd>
}
</dl>
Try this on the client app and you will know if you are getting value in name or not.
Related
Problem: My as an app calls to a downstream web api throw a null exception error after adding my own jwt bearer authentication.
I have a .net 5 web API, call it AppAPI, whose ConfigureServices has the following code:
var accessTokenKey = Convert.FromBase64String(Configuration.GetValue<string>("AccessCodeSecret"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("AccessToken", o =>
{
o.RequireHttpsMetadata = false;
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(accessTokenKey),
ValidateIssuer = false,
ValidateAudience = false
};
})
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd", "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDownstreamWebApi("CommonServicesApi", Configuration.GetSection("CommonServicesApi"))
.AddInMemoryTokenCaches();
//services.AddAuthorization();
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AccessToken")
.Build();
});
I have an /auth endpoint that accepts an access token from Azure AD, and generates a new access token with my own custom claims based on the database. The controller uses an authorize attribute to ensure it uses the correct mechanism:
[Authorize(AuthenticationSchemes = "AzureAd")]
The default policy from above ensures the rest of the endpoints use this access token for every other endpoint that doesnt specify a scheme.
I have a 2nd web API, called CommonServices, that is only accessible from other APIs, not clients directly. So AppAPI uses AddDownstreamwebapi to handle those calls. This worked previously to me adding my own app access tokens, meaning I only had one auth mechanism - AddMicrosoftIdentityWebApi. I started receiving my error when I added my own JwtBearer auth - "AccessToken".
The controller that has the error injects IDownstreamWebApi commonServicesApi. It uses the default auth scheme of "AccessToken". The code looks like this:
var response = await _commonServicesApi.CallWebApiForAppAsync("CommonServicesApi", "AzureAd",
options => { options.RelativePath = "Projects"});
var json = await response.Content.ReadAsStringAsync();
The 2nd parameter "AzureAd" was my attempt to have the commonservicesApi use the correct scheme. I am not even sure if that's the right scheme to use, or if .EnbleTokenAcquisitionToCallDownstreamApi adds a 3rd scheme that should be specified.
It is this call that I receive
System.NullReferenceException: 'Object reference not set to an instance of an object.'
at Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal()
This exception was originally thrown at this call stack:
Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal()
Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(Microsoft.Identity.Web.MergedOptions)
Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplication(Microsoft.Identity.Web.MergedOptions)
Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(string, string, string, Microsoft.Identity.Web.TokenAcquisitionOptions)
Microsoft.Identity.Web.DownstreamWebApi.CallWebApiForAppAsync(string, string, System.Action<Microsoft.Identity.Web.DownstreamWebApiOptions>, System.Net.Http.StringContent)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
I can't seem to figure out what is null, or how to approach solving this problem.
My identityserver4 client looks like this:
new Client {
ClientId = "openIdConnectClient",
ClientName = "Example Implicit Client Application",
//AllowedGrantTypes = GrantTypes.Implicit,
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowOfflineAccess = true,
AllowAccessTokensViaBrowser = true,
AccessTokenLifetime = 30,
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
IdentityServerConstants.StandardScopes.Email,
"role",
"customAPI.write"
},
RedirectUris = new List<string> {"http://localhost:8080/callback"},
PostLogoutRedirectUris = new List<string> {"https://localhost:44330"},
AllowedCorsOrigins = new List<string>
{
"http://127.0.0.1:8080",
"http://localhost:8080",
"*"
},
}
In react application, my userManager class looks like this:
import { createUserManager } from 'redux-oidc';
const userManagerConfig = {
client_id: 'openIdConnectClient',
redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/callback`,
//response_type: 'code id_token token',
response_type: 'token id_token',
scope: 'openid profile email role',
authority: 'http://localhost:50604',
silent_redirect_uri: `${window.location.protocol}//${window.location.hostname}${window.location.port ? `:${window.location.port}` : ''}/silent_renew.html`,
automaticSilentRenew: true,
filterProtocolClaims: true,
loadUserInfo: true,
};
const userManager = createUserManager(userManagerConfig);
export default userManager;
The question is: when i try to call my identityserver4 from the redux-oidc example application. I'm getting the following error:
Client requested access token - but client is not configured to receive access tokens via browser
I hope you understood the question. Please someone help me with this. i have provided the link for this example application bellow.
Redux-oidc example app link
Your code contains two different grant types. The different Grant types in Identity server 4 have different requirements. Here is a bit of information to help you understand the different types you are using. It may also help you understand why you were having this problem.
GrantTypes.ClientCredentials
The Client credentials is the simplest grant type and is used for server to server communication - tokens are always requested on behalf of a client, not a user.
With this grant type you send a token request to the token endpoint, and get an access token back that represents the client. The client typically has to authenticate with the token endpoint using its client ID and secret.
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
GrantTypes.Implicit
The implicit grant type is optimized for browser-based applications. Either for user authentication-only (both server-side and JavaScript applications), or authentication and access token requests (JavaScript applications).
In the implicit flow, all tokens are transmitted via the browser, and advanced features like refresh tokens are thus not allowed. If you want to transmit access tokens via the browser channel, you also need to allow that explicitly on the client configuration:
Client.AllowAccessTokensViaBrowser = true;
new Client
{
ClientId = "mvc",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Implicit,
// where to redirect to after login
RedirectUris = { "http://localhost:5002/signin-oidc" },
// where to redirect to after logout
PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },
AllowedScopes = new List<string>
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile
},
AllowAccessTokensViaBrowser = true
}
I have an ASP.NET SPA with a adal-js based authentication, and an ASP.NET Web Api website with Azure Active Directory auth
Both websites are hosted on Azure, on different hostnames, say
https://foo.azurewebsites.com/ and https://fooapi.azurewebsites.com/
The Web Api website auth is configured as
public partial class Startup
{
public void ConfigureAuth(IAppBuilder app)
{
app.UseWindowsAzureActiveDirectoryBearerAuthentication(
new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
TokenValidationParameters = new TokenValidationParameters() { ValidAudience = ConfigurationManager.AppSettings["ida:Audience"] },
Tenant = ConfigurationManager.AppSettings["ida:Tenant"]
});
}
}
and Main SPA adal.js is initialized as:
var config = {
instance: "https://login.microsoftonline.com/",
tenant: "mytenant",
clientId: "client id of foo registration",
postLogoutRedirectUri: "https://foo.azurewebsites.com/",
cacheLocation: "localStorage"
};
authContext = new AuthenticationContext(config);
// Check For & Handle Redirect From AAD After Login
var isCallback = authContext.isCallback(window.location.hash);
authContext.handleWindowCallback();
var errorMessage = authContext.getLoginError();
if (isCallback && !authContext.getLoginError()) {
window.location = authContext._getItem(authContext.CONSTANTS.STORAGE.LOGIN_REQUEST);
}
// Check if View Requires Authentication
if (!authContext.getCachedUser()) {
authContext.config.redirectUri = window.location.href;
authContext.login();
return;
}
The Tenant is the same for foo and fooapi, the client id is different (one for each app registration).
The authentication flow in the foo web app is performed successfully, but every http request to fooapi returns 401 unauthorized.
How can I make fooapi share the successful authentication of foo ?
Thank you for any hint
You can use the implicit grant flow in AAD so that an ID Token is received and sent in auth header when API call is made. See below links for the details and sample code.
https://azure.microsoft.com/en-gb/documentation/articles/active-directory-authentication-scenarios/#single-page-application-spa
https://github.com/Azure-Samples/active-directory-angularjs-singlepageapp
How you acquire the access token for the web API?
To make sure the request successfully, you need to acquire the token using the resource you config in web API. You can pass the token from here to check whether the aud claim is equal to the value ida:Audience.
And also make sure the token is issued from the tenant you config in web API project since you didn't ignore the tenant verification.
Please configure your web point into endpoints and add it to initialization.
var endpoints = {`enter code here`
"https://yourhost/api": "b6a68585-5287-45b2-ba82-383ba1f60932",
};
adalAuthenticationServiceProvider.init(
{
// Config to specify endpoints and similar for your app
tenant: "52d4b072-9470-49fb-8721-bc3a1c9912a1", // Optional by default, it sends common
clientId: "e9a5a8b6-8af7-4719-9821-0deef255f68e", // Required
//localLoginUrl: "/login", // optional
//redirectUri : "your site", optional
endpoints: endpoints // If you need to send CORS api requests.
},
$httpProvider // pass http provider to inject request interceptor to attach tokens
);
I am relatively new to the concept of middlewares. I am aware that a middleware calls the next middleware when it completes.
I am trying to authenticate a request using either Google or my Identity Server. The user can login on my mobile app with google or a local account. However, I can't figure out how to use both authentication middlewares. If I pass the id_token for google, it passes on the first middleware (UseJwtBearerAuthentication) but fails on the second one (UseIdentityServerAuthentication). How can I make it so that it doesn't throw error when it actually passes on at least 1 authentication middleware? For example, if it passes on the first middleware, the second middleware is ignored?
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
Authority = "https://accounts.google.com",
Audience = "secret.apps.googleusercontent.com",
TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
ValidIssuer = "accounts.google.com"
},
RequireHttpsMetadata = false
});
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:1000/",
RequireHttpsMetadata = false,
ScopeName = "MyApp.Api"
});
Normally, when an authentication middleware is failed(i don't mean throwing exception), this doesn't affect another successful authentication middleware. Probably your second middleware throws an exception(not a validation failure). First check error message and try to resolve it. If you can't, use AuthenticationFailed event to handle error. In this case your code should be something like below:
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
// ...
Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async (context) =>
{
if (context.Exception is your exception)
{
context.SkipToNextMiddleware();
}
}
}
});
However, for your scenerio i wouldn't choose your way. I would use only identity server endpoint. For signing with google you can configure identity server like below:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
AutomaticAuthenticate = false,
AutomaticChallenge = false
});
app.UseGoogleAuthentication(new GoogleOptions
{
AuthenticationScheme = "Google",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = "",
ClientSecret = ""
});
app.UseIdentityServer();
Edit
It seems AuthenticationFailed event couldn't be used for IdentityServer4.AccessTokenValidation. I am not sure but if you will use identity server for only jwt token, you can use UseJwtBearerAuthentication for validation.
Main issue is that I could not find a proper way to logout from identityServer4.
Detailed explanation:
Client side Web application startup.cs contains the following code
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies",
AutomaticAuthenticate = true
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:1941/",//local identityServer4
ClientId = "testsoft",
ClientSecret = "secret",
ResponseType = "code id_token token",
GetClaimsFromUserInfoEndpoint = true,
RequireHttpsMetadata = false,
Scope = { "openid", "profile", "email" },
TokenValidationParameters = new TokenValidationParameters()
{
NameClaimType = "name",
RoleClaimType = "role"
},
AutomaticAuthenticate = false,
AutomaticChallenge = true
});
IdentityServer4 running locally has the client added as below
new Client
{
ClientId = "testsoft",
ClientName = "testsoft",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
ClientUri = "http://localhost:55383/",//clientside web application url
AllowedGrantTypes = GrantTypes.Hybrid,
AllowAccessTokensViaBrowser = true,
RedirectUris = new List<string>
{
"http://localhost:55383/signin-oidc"
},
RequireConsent = false,
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
StandardScopes.Email.Name,
StandardScopes.Roles.Name,
StandardScopes.OfflineAccess.Name,
"api1", "api2",
},
},
I was able to login and display the claims on a Controller-View in MVC like this
[Authorize]
public IActionResult About()
{
return View((User as ClaimsPrincipal).Claims);
}
And the view displayed was like this. Note that there is no id_token
And I was able to logout using cookie as given below
public async Task<IActionResult> LogOut()
{
await HttpContext.Authentication.SignOutAsync("Cookies");
return Redirect("~/");
}
But the problem is I cannot find a way to logout from IdentityServer. The closer I came was to use
/connect/endsession?id_token_hint=...&post_logout_redirect_uri=https://myapp.com
But I could not find a way to get raw id_token in code. In the About() method given above I am only getting the claims (which I think is the decrypted contents of id_token) and in those claims list there is no id_token to be seen. But somehow managed to get the id_token from fiddler at this url http://localhost:55383/signin-oidc and then the logout at identityServer triggered(with the help of the url given above).
I have the following questions:
How to get id_token in code? (instead of manual copy from fiddler)
Is there a better way to logout? Or is there an AspnetCore/Oidc framework method to logout (which in turn call the correct server api with correct parameters) ?
I was able to logout and login several times but the id_token was seen the same on fiddler. eg: Bob user, Alice user both had the same id_token. Cookie was cleared and each time different user was displayed on the view still the id_token was same. shouldn't the id_token be different for each login/user?
Signout url worked even when I gave a random string as id_token. Does this mean that IdentityServer4 logout functionality do not work based on id_token?
For logging out, did you try-
HttpContext.Authentication.SignOutAsync("oidc");
in your client's Logout Action?