I am a bit confused about the use case with reference tokens and the introspection endpoint in a MVC/Webforms scenario.
My basic question is why is the introspection endpoint only setup to allow authentication requests from ApiResource credentials, (api1/apisecret) and not allow for client credentials? I am able to use the code bellow with ApiResource credentials, I just get unauthorized with client credentials. I think this is as designed.
I want to give our systems admins the ability to revoke the tokens and force logout users. My plan is to use the CookieAuthenticationProvider’s OnValidateIdentity to periodically validate the reference tokens and force a logout if needed. I think that I can get similar functionality with short lived self-contained JWT tokens and refresh tokens but liked the simplicity of using reference tokens. I can obviously use two sets of credentials in the same application, one for the client and one ApiResource, but is seams more intuitive to use/manage one set of credentials, the client’s. Should I not use reference tokens for a server-side trusted application?
My clients are setup like the following.
AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
AccessTokenType = AccessTokenType.Reference,
Here are a few notes from my research.
Here are the docs about Introspection Endpoint - http://docs.identityserver.io/en/release/endpoints/introspection.html
Here is Dominick Baier's post about reference tokens - https://leastprivilege.com/2015/11/25/reference-tokens-and-introspection/
Here is an example of the IntrospectionClient call I am using. https://github.com/IdentityServer/IdentityServer4.Samples/blob/release/Clients/src/ConsoleIntrospectionClient/Program.cs
Here is the spec for introspection and as you can see it does not indicate the type of authentication required. - https://www.rfc-editor.org/rfc/rfc7662
To prevent token scanning attacks, the endpoint MUST also require
some form of authorization to access this endpoint, such as client
authentication as described in OAuth 2.0 [RFC6749] or a separate
OAuth 2.0 access token such as the bearer token described in OAuth
2.0 Bearer Token Usage [RFC6750]. The methods of managing and validating these authentication credentials are out of scope of this
specification.
This is a sample of a ApiResource with a secret.
// simple version with ctor
new ApiResource("api1", "Some API 1")
{
// this is needed for introspection when using reference tokens
ApiSecrets = { new Secret("apisecret".Sha256()) }
},
Here is the sample client form the ConsoleIntrospectionClient client.
new Client
{
ClientId = "roclient.reference",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
AllowedScopes = { "api1", "api2.read_only" },
AccessTokenType = AccessTokenType.Reference
},
Related
I have an ASP.NET 6.0 Web API project. I would like to add authentication and authorization to it, but it must use SSO via Azure.
We already have a SPA application that does this, it uses the Angular MSAL library to redirect the user to an SSO Login page, then returns to the SPA with an access token. The access token is then added to the header of each request to the Web API, which uses it to enforce authentication.
Now we want to share our web API with other teams within our organization, and we would like to have that login process just be another API call, rather than a web page.
Conceptually, a client would hit the /login endpoint of our API, passing in a userID and password. The web API would then get an access token from Azure, then return it as the payload of the login request. It's then up to the client to add that token to subsequent request headers.
I have done this with regular ASP.NET Identity, where all of the user and role data is stored in a SQL database, but since our organization uses SSO via Azure Active Directory, we would rather use that.
I have researched this topic online, and so far all of the examples I have seen use a separate SPA, just like we already have. But as this is a web api, not a front-end, we need to have an API method that does this instead.
Is this even possible? I know Microsoft would rather not have user credentials flow through our own web server, where a dishonest programmer might store them for later misuse. I understand that. But I'm not sure there's a way around this.
Thanks.
I believe you are looking for the Resource Owner Password (ROP) flow. You can use IdentityModel.OidcClient to implement it.
Sample code:
public class Program
{
static async Task Main()
{
// call this in your /login endpoint and return the access token to the client
var response = await RequestTokenAsync("bob", "bob");
if (!response.IsError)
{
var accessToken = response.AccessToken;
Console.WriteLine(accessToken);
}
}
static async Task<TokenResponse> RequestTokenAsync(string userName, string password)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync(Constants.Authority);
if (disco.IsError) throw new Exception(disco.Error);
var response = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "roclient",
ClientSecret = "secret",
UserName = userName,
Password = password,
Scope = "resource1.scope1 resource2.scope1",
Parameters =
{
{ "acr_values", "tenant:custom_account_store1 foo bar quux" }
}
});
if (response.IsError) throw new Exception(response.Error);
return response;
}
}
Sample taken from IdentityServer4 repository where you can find more ROP flow client examples.
I would recommend that you don't go with this implementation and instead have all clients obtain their access tokens directly from Azure AD like you did with your Angular SPA.
There is a C# application under development that is supposed to be a part of a bigger backend application to process some data. This application is supposed to obtain a token from Azure AD B2C and send it to an HTTP-triggered function where it is supposed to be validated by the following code:
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
$"{_authenticationSettings.Authority}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
_validationParameters = new TokenValidationParameters
{
IssuerSigningKeys = config.SigningKeys,
ValidateAudience = true,
// Audience MUST be the app ID aka clientId
ValidAudience = _authenticationSettings.ClientId,
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateLifetime = true
};
var tokenHandler = new JwtSecurityTokenHandler();
var result = tokenHandler.ValidateToken(authHeader.Parameter, _validationParameters, out var jwtToken);
First, we thought that obtaining an access token from Microsoft Graph API using MSAL would help us but the C# code above threw an invalid signature exception which we discovered makes sense due to this GitHub post. Apparently, we need to obtain an id_token instead in the application and send it to the HTTP-triggered function for validation by the code snippet above.
The application cannot obtain the id_token because it's not supposed to launch Azure AD B2C's login UI to have a user sign-in and redirect it through a URL. What is the solution to this problem so that the application would obtain a token without a UI and send that to the http-triggered function for validation?
Obtaining a token for the AAD B2C tenant without UI is possible in two ways and you should probably pick one depending on what exactly you want to achieve:
user token - by using Resource Owner Password Credentials flow - https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-ropc-policy. This flow is deprecated though and mentioned usually in legacy application context
server-side application token - by using Client Cretendial flow - this on the other hand requires using requests specific for AAD but with AAD B2C tenant - https://learn.microsoft.com/en-us/azure/active-directory-b2c/application-types#daemonsserver-side-applications
I'm also not quite sure why should you use id_token for that. If the application needs to authorize the request to the function with the token then it should be an access token regardless of how the token is retrieved (interactive UI or not).
In a angularjs application, i use adal and adal-angular libraries to authentify user on Azure AD. On backend I use OWIN middleware in my webAPI to add custom claims with :
app.UseWindowsAzureActiveDirectoryBearerAuthentication(new WindowsAzureActiveDirectoryBearerAuthenticationOptions
{
Audience = ConfigurationManager.AppSettings["ida:Audience"],
Tenant = ConfigurationManager.AppSettings["ida:Tenant"],
Provider = new OAuthBearerAuthenticationProvider()
{
OnValidateIdentity = async context =>
{
if(!context.IsValidated)
return;
var userManager = context.OwinContext.Get<UserManager>();
string email = context.Ticket.Identity.GetClaimValue(System.Security.Claims.ClaimTypes.Email);
User user = userManager.GetByEmail(email);
context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.UserId, user.Id.ToString(CultureInfo.InvariantCulture));
}
}
});
It's work for the current request, but how to add the claim in the bearer send by httpinterceptor of adal-angular for the next requests?
To answer your direct question: you cannot modify the access token, period.
It has a digital signature which will no longer be valid if you change anything in the token.
It's a security measure that prevents tampering.
Your OWIN middleware downloads the public keys of the signing key pairs from Azure AD on startup, and uses those to validate tokens.
Now if you stored your internal ids as extension attributes,
it would actually be possible to tell Azure AD to include that in the tokens.
This feature is in preview though, and not recommended for production use: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-optional-claims#configuring-custom-claims-via-directory-extensions.
Here you would set the internal id as an extension on the User entity, and have it included for access tokens to your API.
A pragmatic solution to your issue might be an in-memory cache.
How often do emails change?
We are using EntityFramework Core to store configuration data. What is use of store Client secrets along with clients? Or can we add/modify clients secrets later on?
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
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" },
AccessTokenLifetime=3600
}
You will need the secret to access the Token Endpoint when programmatically requesting tokens.
For example, you can use the IdentityModel library:
var client = new TokenClient(
doc.TokenEndpoint,
"client_id",
"secret");
var response = await client.RequestClientCredentialsAsync("scope");
var token = response.AccessToken;
Although Gavin Sutherland's answer explains how to use client secrets, I believe the question is more about why they exist. Basically, think of the client ID and client secret as a username / password enabling a specific client application to initiate OpenID Connect requests (with the other IdentityServer Client configuration elements defining which requests are permitted).
You can change secrets, but synchronizing the secret between IdentityServer and the client application is a manual process. (It isn't like certain key rotation scenarios where you can have more than one secret active for awhile, removing the old one after all clients have updated, if that's what you're thinking.)
If you look at any OIDC / OAuth2 third-party identity provider (Google, Facebook, etc.) they all operate by issuing client IDs and secrets associated with specific clients (normally this means specific domains).
I'm getting my knickers in a twist trying to understand how to call an API protected via IdentityServer4.
Basically, I have the following sites:
- an IdentityServer application,
- a web API and
- a client web application.
My setup is just like the IdentityServer samples here.
I define a Client which represents my client web application, and an APIResource which represents my Web Api.
From within my client web application I want to make an HTTP call to the WebAPI, but I want to appear as if I am the logged in user, so I want to make the 'email' scope available to the Web Api.
The way I'm doing from within the Web Application is to grab the 'access_token', and to pass it to the Web API:
var accessToken = await httpContextAccessor.HttpContext.Authentication.GetTokenAsync($"access_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
This allows me to call the Client, so the authorization step is working, but the User Claims on the Web Api do not have the appropriate scopes.
Am I doing something wrong?
The access_token can contain claim information in IdentityServer4. The required claims must be specified in the ApiResource definition.
Otherwise, you have to send a JWT id_token along with the request.
new ApiResource(ApiResourceNames.SomeApiAccess, "Access to some api.", new List<string>(){
new IdentityResources.OpenId().Name,
new IdentityResources.Profile().Name,
new IdentityResources.Email().Name
}),
You can add scopes in your web api like
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "https://demo.identityserver.io",
ApiName = "api1",
AllowedScopes = { "api1.read", "api1.write" }
});
How did you configure your web api? Post the code if it still doesn't work!