I would like to make an identity service with IdServer4 that outsources the 'authentication' part to Auth0 - Auth0 deals with Single Sign On and other stuff and does a great job - so no need to reinvent the wheel. But I would like to embed this in an identity server (pref. IdentityServer4), that handles authentication via Auth0 and handles authorization itself (claims and scopes) for users & machines.
Machines would acquire their token through the tokenClient via so-called Client Credentials (https://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html).
public static IEnumerable<Client> Clients =>
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" }
}
};
The machine 2 machine auth works. But how can the identity server make sure that 'users' log in via Auth0 (SSO) and then get an access token from IdentityServer4 itself (just like the machines), instead of getting the token from Auth0 itself. I have implemented Auth0 as a external ID Provider:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect("Auth0", options => {
options.Authority = "auth0domain";
options.ClientId = "clientId";
options.ClientSecret = "secret";
...
});
For the rest, see : https://auth0.com/blog/using-csharp-extension-methods-for-auth0-authentication/
When triggering the Authentication via await HttpContext.ChallengeAsync(); the user can login. And afterwards he or she can logout. This works fine. But the user acquires an access token from Auth0 itself and I would like to replace it by a token generated by IdSrv4. Is this possible?
You need to use Identity Server as the base authentication server and configure SSO as an external login. Just like when you login a website using google, facebook, etc. The only consideration is that the SSO server should support a standard like OIDC. Take a look at
https://docs.duendesoftware.com/identityserver/v6/ui/login/external/
https://docs.identityserver.io/en/latest/topics/signin_external_providers.html
You can do any authentication stuff (e.g. adding claims) at login callback handler
Are you tied to IdentityServer4? Might be worth looking into OpenIddict as an alternative. I've just implemented this and an API secured by it using the provided tokens - worked a treat.
Apologies if I've missed the point of your question
Related
I have two services implemented as Web APIs in ASP.NET Core, dockerized and orchestrated (docker-compose and Kubernetes). One service provides authentication and authorization (authNZ, IdentityService), and the other provides resources to authNZ'd users (ResourceService).
Any authenticated user (OIDC-based authentication against Google) has a JWT token, which they can use to add as a bearer token to their API calls to the ResourceService.
Q1: Should ResourceService validate every token calling IdentityService?
Supposing the answer is yes, the authorization middleware of the ResourceService fails with the following error when validating authNZ to an API endpoint.
Authorization failed. These requirements were not met:
DenyAnonymousAuthorizationRequirement: Requires an authenticated user.
Exception occurred while processing message.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://identity/.well-known/openid-configuration'.
Q2: Do I need to implement the .well-known endpoints in the IdentityService to validate the JWT tokens?
I am aware of the complexities and challenges associated with implementing an Identity service without leveraging dedicated libraries (e.g., IdentityServer4). However, given some licensing issues, I cannot leverage such libraries.
I am sharing some setups I find most relevant, but happy to share other parts of the code if needed.
AuthNZ configuration of the ResourceService:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://identity";
options.Audience = "https://resource";
});
AuthNZ configuration of the IdentityService:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = "https://identity";
options.Audience = "https://resource";
})
.AddCookie(options =>
{
options.LoginPath = "/api/v1/authnz/signin";
})
.AddGoogle(GoogleDefaults.AuthenticationScheme, options =>
{
options.ClientId = "...";
options.ClientSecret = "...";
options.CallbackPath = "/authnz/google/callback";
});
Clients and APIs needs to be able to download the configuration document at https://identity/.well-known/openid-configuration and it should be a public document that is not protected in anyway.
When you get this error:
Exception occurred while processing message. System.InvalidOperationException: IDX20803: Unable to obtain configuration from
It is typically because the service can't reach the IdentityProvider. Typically it s a HTTPS or networking issue in your backend.
Every service that provides tokens should preferably expose a configuration document. If you are using IdentityServer, this is built-in.
The alternative is that you provide the public signing key manually to each service.
To fix Q1, the it all depends on the deployment and network setup of your services. It is typically either due to HTTPS certificate issues or if one service can't talk to other services behind the firewall. Sometimes you might need to use a different "service name" when you need to send HTTP(s) requests behind the firewall.
How do I get OAuth2 Access Token and Refresh token for C# ASP.NET Core Web API client to authenticate YouTube Data API v3
There is no UI for a username to manually enter their username and password, then receive code to get the token in this scenario. No redirect_uri is required.
How can I get the access token and refresh token
I once solved a similar issue with Microsoft Azure AD, solution on stackoverflow
I just can't find any information regarding Google Cloud Platform .NET clients for this scenario
You can not use client login (username and password) with any Google api since 2015. You will need to use Oauth2 in order to authenticate your user.
You will need to configure the library first.
public void ConfigureServices(IServiceCollection services)
{
...
// This configures Google.Apis.Auth.AspNetCore3 for use in this app.
services
.AddAuthentication(o =>
{
// This forces challenge results to be handled by Google OpenID Handler, so there's no
// need to add an AccountController that emits challenges for Login.
o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// This forces forbid results to be handled by Google OpenID Handler, which checks if
// extra scopes are required and does automatic incremental auth.
o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
// Default scheme that will handle everything else.
// Once a user is authenticated, the OAuth2 token info is stored in cookies.
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogleOpenIdConnect(options =>
{
options.ClientId = {YOUR_CLIENT_ID};
options.ClientSecret = {YOUR_CLIENT_SECRET};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseAuthorization();
...
}
Then you can make any call you like to the YouTube API. When this endpoint is hit the user will be prompted to consent to authorization.
[GoogleScopedAuthorize(YouTubeService.ScopeConstants.Readonly)]
public async Task<IActionResult> YouTubeCall([FromServices] IGoogleAuthProvider auth)
{
GoogleCredential cred = await auth.GetCredentialAsync();
var service = new YouTubeService(new BaseClientService.Initializer
{
HttpClientInitializer = cred
});
// your call to the youTube service here.
}
I recommend having a look at the sample for Asp .net core however it is in google drive you will need to alter it.
The client library should be handing all the access tokens and refresh tokens for you, but if you really want to access them there is a bit of information on how here #1725
I am trying to authenticate CORS origin requests and set Claims principle with the user of internal company single sign on utility. I have the current setting so far, the cookie will never get created on the domain set at the authentication setup.
I have an Angular client application and .Net Core 3.0 Webapi, the requirement is for the client to be able to set authentication for future api calls.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = "access_token";
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.Domain = "localhost:xxxx";
});
//CORS
services.AddCors(options =>
{
options.AddPolicy(
"AllowOrigin",
builder => builder.WithOrigins("localhost:xxxx")
.AllowCredentials()
.AllowAnyHeader()
.AllowAnyMethod());
});
//Sign In
HttpContext.SignInAsync(
scheme: CookieAuthenticationDefaults.AuthenticationScheme,
principal: new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme)),
properties: new AuthenticationProperties { ExpiresUtc = DateTime.Now.Add(120) });
I am testing this all on local so both URLS are localhost with different ports
Angular is hosted: http://localhost:xxxx
WebAPi is hosted :http://localhost:xxx2
http request from Angular to webapi is http://localhost:xxx2/api/auth which has the SignInAsync call, the company single sign does a username but the cookie never gets created. If I remove the options.Cookie.Domain = "localhost:xxxx"; the cookie does get created on the webapi domain http://localhost:xxx2. I must be missing something here.
After reading up some other posts on stackoverflow , it tuned out that AllowAllOrigins will only fix this problem but poses a threat.
So I ended up fixing this issue with JWT - setting authorization token for every request sent from client interface. This issue was caused due to fact that the client and WebApi are hosted on different domains.
Im currently struggling to connect a ASP.NET Core 2.2 Web API to an existing Azure AD. I based my configuration upon this sample code by the ASP.NET Core team. Cookies were replaced with JWTs.
Unable to retrieve document from metadata adress
Now I face the following error message:
IOException: IDX10804: Unable to retrieve document from: {MetadataAdress}.
- Microsoft.IdentityModel.Protocols.HttpDocumentRetriever+<GetDocumentAsync>d__8.MoveNext()
- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
- System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
- Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectConfigurationRetriever
+<GetAsync>d__3.MoveNext()
- System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
- System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
- Microsoft.IdentityModel.Protocols.ConfigurationManager+<GetConfigurationAsync>d__24.MoveNext()
When I call the URL directly, I receive an instant response with the configuration file. However, the code does not seem to be able to do it. Im not sure what the reason could be.
Azure AD Configuration Syntax
The most likely cause of this issue is a configuration mistake. Maybe I have mistaken a field's syntax or am missing an important value.
Connection Info Fields
The connection info fields are provided like this:
TenantId: {Tenant-GUID}
Authority: https://login.microsoftonline.com/{TenantId}
Resource: https://{resource-endpoint}.{resource-domain}
ClientId: {Client-GUID}
ClientSecret: {ClientSecret}
Service Configuration
The authentication service configuration in the Startup.cs looks like this:
services
.AddAuthentication(options => {
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer()
.AddOpenIdConnect(options => {
options.ClientId = this.ClientId;
options.ClientSecret = this.ClientSecret;
options.Authority = this.Authority;
options.Resource = this.Resource;
options.ResponseType = OpenIdConnectResponseType.CodeIdToken;
options.SignedOutRedirectUri = "/signed-out";
options.Events = new OpenIdConnectEvents()
{
OnAuthorizationCodeReceived = async context =>
{
var request = context.HttpContext.Request;
var currentUri = UriHelper.BuildAbsolute(
request.Scheme, request.Host, request.PathBase, request.Path
);
var credentials = new ClientCredential(this.ClientId, this.ClientSecret);
var authContext = new AuthenticationContext(
this.Authority,
AuthPropertiesTokenCache.ForCodeRedemption(context.Properties)
);
var result = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.ProtocolMessage.Code,
new System.Uri(currentUri),
credentials,
this.Resource
);
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
// Custom
options.MetadataAddress = $"{this.Authority}/federationmetadata/2007-06/federationmetadata.xml";
options.RequireHttpsMetadata = false; // Dev env only
}
Existing APIs
There is a bunch of existing Web APIs that connect to this Azure AD. Sadly, they are all using the full .NET Framework. They use the UseWindowsAzureActiveDirectoryBearerAuthentication method from the Microsoft.Owin.Security.ActiveDirectory namespace's WindowsAzureActiveDirectoryBearerAuthenticationExtensions.
Another thing they use is the HostAuthenticationFilter with an authentication type of Bearer.
Questions
What is the problem?
How can I resolve this issue?
How can I use these components together?
ASP.NET Core 2.2
JWT Bearer Authentication
Azure AD (token validation + claim extraction only - creation is handled by other service)
You are using OpenIDConnect libraries and point them to WS-Federation metadata (/federationmetadata/2007-06/federationmetadata.xml). This is not going to work.
The correct metadata endpoint for OpenIDConnect is /.well-known/openid-configuration. This is described here. Change that first, and then return cookies.
UPDATE
What I oversaw, was that you are protecting WebAPI. You say the middleware to use JwtBearer as default authentication cheme, but you also include a challenge scheme to be OIDC. That doesn't really make sense for me. Why do you want an OIDC challenge scheme for an WebAPI?
Here you can find the ASP.NET Core samples about JwtBearer. Here the Azure AD samples demoing WebApp calling WebApi (also bearer for the WebAPI, OIDC for the App FrontEnd.
There are no samples for JWT Bearer Auth using OIDC challenge. Why do you want to implement that? What is the case? You might be looking at implementing multiple Authentication schemes, which is possible. But not having one scheme for Authentication and another for challenge...
If by updating/removing the wrong metata changes the error message, include that in your original question. As it is now - the pure error message is that OIDC Middleware cannot parse WS-Federation metadata. Which is expected.
Source of the problem
After some testing I managed to identify the problem: Apparently the main cause of this issue was network related. When I switched from our company's to an unrestricted network the authentication was a success.
The fix
I had to configure a proxy and provide it to the JwtBearer and OpenIdConnect middleware. This looks like this:
var proxy = new HttpClientHandler
{
Proxy = new WebProxy("{ProxyUrl}:{ProxyPort}") { UseDefaultCredentials = true; },
UseDefaultCredentials = true
};
services
.AddJwtBearer(options => {
// ... other configuration steps ...
options.BackchannelHttpHandler = proxy;
})
.AddOpenIdConnect(options => {
// ... other configuration steps ...
options.BackchannelHttpHandler = proxy;
})
Metadata adress
#astaykov was right that the metadata adress is indeed incorrect. I had this feeling as well but kept it as previous APIs were running successfully with it. During problem testing I removed it, too, but it would not make a difference due to the network issues.
After the network issues were resolved, using the default metadata adress worked. The custom one failed - as expected when using a different authentication schema.
I'm trying to use Identity Server 4 to protect my API. Now I have gone through all the documentation at http://docs.identityserver.io/en/release/quickstarts/1_client_credentials.html and I have set up a few successful demos. However, there is one thing that I am failing to understand.
For example, First, we need to define a client on the IS4 that looks like this:
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" }
}
Then on the API we protect it by adding the IdentityServer4.AccessTokenValidation package and adding configuration to startup.cs
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
And then finally we add app.UseAuthentication();
Now, this all works when ran, however, the part that I don't understand is where is the secret defined in the API. As you can see, the client clearly expects a secret and yet I don't define this secret anywhere on my API. I also don't define anywhere on the IS4 anything about my API to say that you are protecting the API from this URI or something along those lines.
So how does this actually works in terms of IS4 knowing about the API and authenticating its requests?
EDIT:
To clarify some confusion, yes there is a client that I opted out in code above and I see now that I shouldn't, and in there I provide a secret, but I'm still not understanding how does the IS4 knows to protect my specific API. What if the request came from www.somerandomapi.com? From what I"m reading it would work regardless. Based on what you wrote, it does make sense that the client is passing the secret, but nowhere in my code is the IS4 told which API to protect.