Identity Server 4 Protect an API - c#

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.

Related

IdentityServer4 + Auth0

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

Sign Out of a Blazor App using the OIDC Authentication Scheme

We have a .NET Core 6 Blazor Server App. We login with our own Identity Provider using OIDC. We are having an issue signing out.
We have set up our authentication using the following code block.
builder.Services
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddCookie()
.AddOpenIdConnect(opts => {
opts.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.RequireHttpsMetadata = !isDebug;
opts.ClientId = "user-accounts-app";
opts.CallbackPath = "/signin-oidc";
opts.ResponseType = OpenIdConnectResponseType.Code;
opts.Authority = authority;
opts.ClientSecret = builder.Configuration["CLIENT_SECRET"];
var scopes = new List<string>() {
"openid", "profile", "email", "phone", "offline_access"
};
foreach(var s in scopes)
{
opts.Scope.Add(s);
}
});
The discovery document does include an end_session_endpoint; however, the endpoint is never hit. We attempt to signout from a razor page with
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
// This line does not work
await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties
{
RedirectUri = "http://mydomainhere.com/our/path/here",
});
Running that 2nd SignOutAsync seems to do nothing. The Identity Provider is not hit at the end session endpoint and nothing happens on our logout page. Our session is not cleared from the IDP.
Additionally, our cookies for the blazor app are not entirely cleared. We have a ton of lingering .AspNetCorrelation.hash<hash-here> with path /signin-oidc (tried to get a screenshot but SO is having server errors with those right now). But the .AspNetCore cookie is cleared successfully by the first SignOutAsync call.
I'm not sure what the behavior of the second SignOutAsync is supposed to be. Would it redirect the user to the logout url of the IDP? Or does it do that in the background? Are we missing some configuration in our call to AddOpenIdConnect() to handle sign out?
Looks like we were just missing an OIDC sign out scheme.
opts.SignOutScheme = OpenIdConnectDefaults.AuthenticationScheme;
This is all we needed to get it working.
ASP.net will use the sign in scheme if no sign out scheme is specified. The sign in scheme is cookie which is a bit misleading because the OpenID authority is actually the one you're signed into. Signing in leads to the cookie being created to store the auth token provided by that authority (so you are effectively signed into the client app). If you sign out with a cookie scheme then only the cookie is destroyed -- you are signed out of the client, but not the authority. The next time you come to a page, you just get a new cookie because you're already signed into the authority.
The sign out scheme above therefore signs out of the authority, not just the client. I'm not sure if my colleague who figured it out also added a step of removing the cookie. I will edit this with details if I find out they did or not. It may somehow be magically handled by the asp framework.

Google API .NET Client - How do I get OAuth2 Access Token and Refresh token for C# ASP.NET Core Web API client to authenticate YouTube Data API v3

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

Azure AD Authentication in ASP.NET Core 2.2

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.

401 Unauthorized when using the generic OAuth Middleware with ASP.NET 5

I am trying to hook up a website that I am building to FitBit using ASP.NET 5 (rc1-final), Identity and the MS.AspNet.Authentication.OAuth middleware. I am intending to use the Authorization Grant Flow for OAuth 2.0. I have the app set up (details below) on FitBit, and my Startup.cs looks like:
app.UseIdentity();
app.UseOAuthAuthentication(options =>
{
options.AuthenticationScheme = "FitBit-AccessToken";
options.AuthorizationEndpoint = "https://www.fitbit.com/oauth2/authorize";
options.TokenEndpoint = "https://api.fitbit.com/oauth2/token";
options.SaveTokensAsClaims = true;
options.CallbackPath = new PathString("/signing-fitbit-token/");
options.ClientId = "[MY ID STRIPPED OUT]";
options.ClientSecret = "[MY SECRET STRIPPED OUT]";
options.DisplayName = "FitBit";
options.Scope.Add("activity");
options.Scope.Add("heartrate");
options.Scope.Add("location");
options.Scope.Add("nutrition");
options.Scope.Add("profile");
options.Scope.Add("settings");
options.Scope.Add("sleep");
options.Scope.Add("social");
options.Scope.Add("weight");
options.AutomaticAuthenticate = true;
});
When I click the login button, I am directed to the authorization page on FitBit, but when I click Authorize, I am greeted with the ASP.NET dev error page:
An unhandeled exception occurred while processing the request.
HttpRequestException: Response status code does not indicate success: 401 (Unauthorized)
System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()
I did read here that with some OAuth endpoints (namely Yahoo) they don't like localhost. So, I tried it both with localhost, and modifying my hostfile to a different domain. I have ensured that the redirect url that I am passing in is what is registered for the app at FitBit.
This error is coming from my website, and is getting through to the point where its exchanging the code for the access token. I have fiddler open I'm a bit lost as to where to go from here. I am running on http (since this is local dev and I don't have an ssl cert yet), but I wasn't entirely sure if that mattered.
By default, the OAuth2 generic middleware sends the client credentials by flowing them in the request form (encoded using application/x-www-form-urlencoded).
Sadly, Fitbit only supports basic authentication: since the credentials are not flowed in the Authorization header, Fitbit treats your token request as unauthenticated and rejects it.
Luckily, this is something the dedicated Fitbit social provider (internally based on the OAuth2 generic middleware) will handle for you: https://www.nuget.org/packages/AspNet.Security.OAuth.Fitbit/1.0.0-alpha3
app.UseFitbitAuthentication(options => {
options.ClientId = "230H9";
options.ClientSecret = "ae7ff202cg5z42d85a3041fdc43c9c0b2";
});
Something is going wrong with the OAuth request to FitBit, you need to debug that request and see why you got a 401 back from FitBit.

Categories