OpenID Connect from Swagger UI with PKCE and Okta in .Net Core - asp.net-core

After stepping around controller authorization in the debugger for the past 4 weeks, I finally decided to tackle OpenID Connect authentication in my Swashbuckle-supported .NetCore 5 API. I wish I hadn't, because I spent almost a day so far without a working solution.
Here is a brief recap.
Support for OpenID Connect in Swagger-UI is very recent. The only place where I found this information was in Helen's comment to this question. Swagger Ui 3.38.0 is only available in Swashbuckle 6.0.7.
Once upgraded to the latest Swashbuckle, I started to see a bunch of "discovered" authorization options in Swagger UI. Alas, PKCE does not appear to be in use, based on the error, even though I explicitly set it in Startup.cs:
.UseSwaggerUI(c => c.OAuthUsePkce());
Also, the ClientSecret there does not make sense, because PKCE is supposed to replace this (and I actually don't have a client secret).
My question, does anybody have OpenID Connect with PKCE and Okta working in Swagger UI?
Auth ErrorError, error: invalid_client, description: Browser requests to the token endpoint must use Proof Key for Code Exchange.

I've recently sitched from an implicit flow to code+pkce flow. I ran into the same issue. The key was to configure the token endopoint url. Swagger UI will still show you the client credentials input box, but you can leave this empty when authorizing.
var securityDefinition = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Scheme = "Bearer",
In = ParameterLocation.Header,
Name = "Authorization",
Flows = new OpenApiOAuthFlows
{
AuthorizationCode = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri(azureAdOptions.AuthorizeEndpoint),
TokenUrl = new Uri(azureAdOptions.TokenEndpoint),
Scopes = azureAdOptions.Applications["Api"].Scopes.ToDictionary(e => e.Value, e => e.Key)
}
}
};
c.AddSecurityDefinition(
"oauth2",
securityDefinition);
I obviously still have to enable pkce support on the SwaggerUiOptions
internal static void ConfigureUi(SwaggerUIOptions c, IConfiguration configuration, string apiName, string environmentName)
{
c.OAuthUsePkce();
}
I use Azure AD, here are the values I've used:
AuthorizationUrl: https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize
TokenUrl: https://login.microsoftonline.com/organizations/oauth2/v2.0/token
Scopes: custom-value
The commit below contains all the details of how it's implemented. It also contains a test sample.
Add support to PKCE for SwaggerUI & update OAuth2Integration sample

Related

How can I change the 'typ' of a token provided by Azure AD?

I have a project setup like this:
React frontend
-> authenticates against...
Identity Server
-> which redirects to...
A Microsoft login
I'm using a Clients Credential Provider and it works great - the IS4 redirects to MS login, and then gets redirected with the access token back, which is then passed on to the React app.
Now, I've been tasked with creating a feature to change the user's password. I'm trying to do this by sending the old+new password to IS4, and then calling the MSGraphClient, but I couldn't make it work.
I've tried the Username/Password provider, because I have all the info needed, but I need to change stuff on the ActiveDirectory settings to make my app public. But even then, I don't like that solution.
I've also tried with the On-behalf-of provider, this is the code:
var scopes = new[] { "User.Read",
"Directory.AccessAsUser.All" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "~~";
// Value from app registration
var clientId = "~~";
var clientSecret = "~~";
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
// This is the incoming token to exchange using on-behalf-of flow
var oboToken = HttpContext.Request.Headers.First(h => h.Key == "Authorization").Value.ToString().Replace("Bearer ", "");
var cca = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
// DelegateAuthenticationProvider is a simple auth provider implementation
// that allows you to define an async function to retrieve a token
// Alternatively, you can create a class that implements IAuthenticationProvider
// for more complex scenarios
var authProvider = new DelegateAuthenticationProvider(async (request) => {
// Use Microsoft.Identity.Client to retrieve token
var assertion = new UserAssertion(oboToken);
var result = await cca.AcquireTokenOnBehalfOf(scopes, assertion).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
var graphClient = new GraphServiceClient(authProvider);
And it kinds of work, because the request is made, but the server throws an error:
AADSTS5002727: Invalid JWT header type specified. Allowed types: 'JWT','http://openid.net/specs/jwt/1.0'.
I checked my token on JWT.io, and the typ is at+jwt... Why? Why is MS sending me a type of token that it doesn't support? How can I change it from my side so it's a plain JWT?
Thanks for any advice, and any other possible solution for this.
To resolve the error "AADSTS5002727: Invalid JWT header type specified. Allowed types: JWT,http ://openid.net/specs/jwt/1.0" , please try the below if helpful:
Please check the version of .Net core you are currently using to generate the token. Try using .Net core 2.2 with IS4.
Try setting IdentityServerOptions.AccessTokenJwtType to empty string or JWT on IdentityServerOptions.
In the mentioned code, replace var oboToken variable directly with the value of token.
var oboToken = "JWT_TOKEN_TO_EXCHANGE";
Please note the below point from MsDoc :
Don't attempt to validate or read tokens for any API you don't own,
including the tokens in this example, in your code. Tokens for Microsoft services can use a special format that will not validate as
a JWT, and may also be encrypted for consumer (Microsoft account)
users
If still the error persists, try upgrading clients to a new token validation library that works with the new style tokens.
Please check whether the below links give you any pointer to resolve the issue:
JWT Token always Invalid · Issue #905 · openiddict/openiddict-core · GitHub
IdentityServer .Net Core 3.0 & Owin/Katana Token validation · Issue #3705 · IdentityServer/IdentityServer4 · GitHub

ServiceStack API aspnet core with Azure AD B2C returns 401 for request even with bearer token

I have a working ServiceStack API that authenticates against a AzureAD tenant. We are trying to move this to start using Azure B2C. The application is build with c# and runs on net 5.0. I've managed to change the configuration to use the 'correct' config. I'm then using Postman to get my access token from my tenant suing the authorization code flow.
However, when i make a request to the api, the response is always a 401 status code.
Where in the servicestack code can I put a break point to see why this failure is happening? I have tried multiple places in our AppHostConfigurator.cs/AppHost.cs files, but the break points doesn't appear to display why a 401 is being sent back as a response. I'm sure it's something related to wrong claims/roles expected etc, maybe the Azure ADB2C application being setup incorrectly, but obviously i need to know exactly so that i can resolve.
I'm setting up the authentication like this:
private static void ConfigureAuthentication(IAppHost host)
{
var authProviders = new List<IAuthProvider> {new NetCoreIdentityAuthProvider(host.AppSettings)};
if (host.AppSettings.GetAllKeys().Contains("AzureAdB2C"))
{
var debugMode = host.AppSettings.Get(nameof(HostConfig.DebugMode), false);
var azureSettings = host.AppSettings.Get<AzureAdB2COptions>("AzureAdB2C");
var jwt = azureSettings.GetB2CJWTProviderReader(debugMode);
jwt.PopulateSessionFilter = (session, payload, request) =>
{
if (session.Email == null && payload.ContainsKey("upn") && payload["upn"].Contains("#"))
session.Email = payload["upn"];
if (session.UserName == null && payload.ContainsKey("unique_name"))
session.UserName = payload["unique_name"];
};
authProviders.Add(jwt);
}
var auth = new AuthFeature(() => new AuthUserSession(), authProviders.ToArray())
{
HtmlRedirect = "/account/signin",
HtmlLogoutRedirect = "/account/signout",
IncludeAssignRoleServices = false,
IncludeRegistrationService = false
};
// remove default service authentication services
auth.ServiceRoutes.Remove(typeof(AuthenticateService));
host.Plugins.Add(auth);
}
We are using swagger as well to call the API (which works as expected). This question is more about that requests that are submitted with a bearer token.
thanks
Please refer to this existing answer for examples of how to validate why a 3rd Party JWT Token is invalid with ServiceStack's JWT Auth Provider.

Getting 401 when calling ASP.NET Core 2.1 API. Is this a CORS issue?

I've been trying to resolve a 401 error for the past couple days without any success.
ASP.NET Core 2.1 API hosted behind IIS. I'm trying to access the API with windows authorisation but I'm being challenged with a login prompt. If I don't enter a username and password I get a 401 error (screenshot attached). I've followed all the articles I could find and believe I have CORS configured correctly.
Based on the screenshot does this look like a CORS issue? I'm testing via swagger and am calling from what I believe is the same domain. Does anyone have any suggestions regarding what the issue may be?
From what I see in this screenshot, everything works fine. 401 is a desirable error in this scenario, it is also proof that you don't have any problems with CORS because the API responds to your requests in an adequate way.
To break through to Api you should focus on the "Response Headers" section in which the type of authentication is defined as BEARER.
From this we can conclude that authentication is token based and in practice works as follows:
By correctly logging in through Windows Authentication, WebAPI provides a response token in header that identifies you as a user.
In order to have access to API, you should store this token locally, and then make use of it by adding it to header section of each request.
To learn more about token based authentication in swagger, check
https://swagger.io/docs/specification/authentication/bearer-authentication/
To understand how tokens works, check https://jwt.io/
Below is an example of how to achieve the intended goal by configuring swagger in the startup class of asp net core application.
public void ConfigureServices(IServiceCollection services)
{
//other code removed for brevity
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info { Title = "My App API", Version = "v1" });
c.CustomSchemaIds((type) => type.FullName);
c.DescribeAllEnumsAsStrings();
c.DescribeAllParametersInCamelCase();
c.EnableAnnotations();
c.OperationFilter<FormFileOperationFilter>();
var apiXmlDocFileName = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var apiXmlDocFilePath = Path.Combine(AppContext.BaseDirectory, apiXmlDocFileName);
c.IncludeXmlComments(apiXmlDocFilePath);
c.AddFluentValidationRules();
c.AddSecurityDefinition("Bearer", new ApiKeyScheme() //this is desireable line
{
Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = "header"
});
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> { { "Bearer", Enumerable.Empty<string>() } });
});
}
After implementing this you will be able to add the token to all requests directly from the swagger UI.
You can also achieve the intended goal using Postman
https://learning.getpostman.com/docs/postman/sending-api-requests/authorization/
Hope it Helps.

OAuth 2.0 , Azure AD and OpenId Connect

I am still trying to understand OAuth 2.0 flows with ASP.Net Core 2.0. The default code (see below) which was provided by Microsoft works well with Azure AD authentication using OAuth 2.0 and OpenId Connect.
I am actually testing the Authorization Code flow.
I am trying to change the code below so that it doesnt use OpenId Connect but instead use the plain OAuth. (You may want to ask why, the vendor I am working with is not supporting the OpenId Connect yet).
So I need to use plain OAuth to enable to Authorization Code flow using Azure AD.
services.AddAuthentication(auth =>
{
auth.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(opts =>
{
Configuration.GetSection("Authentication").Bind(opts);
opts.Events = new OpenIdConnectEvents
{
OnAuthorizationCodeReceived = async ctx =>
{
HttpRequest request = ctx.HttpContext.Request;
//We need to also specify the redirect URL used
string currentUri = UriHelper.BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path);
//Credentials for app itself
var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret);
//Construct token cache
ITokenCacheFactory cacheFactory = ctx.HttpContext.RequestServices.GetRequiredService<ITokenCacheFactory>();
TokenCache cache = cacheFactory.CreateForUser(ctx.Principal);
var authContext = new AuthenticationContext(ctx.Options.Authority, cache);
//Get token for Microsoft Graph API using the authorization code
string resource = "https://bupaau.onmicrosoft.com/4fa4b4a7-d34f-49af-8781-c8b39f0cf770";
AuthenticationResult result = await authContext.AcquireTokenByAuthorizationCodeAsync(
ctx.ProtocolMessage.Code, new Uri(currentUri), credential, resource);
//Tell the OIDC middleware we got the tokens, it doesn't need to do anything
ctx.HandleCodeRedemption(result.AccessToken, result.IdToken);
}
};
});
How do I turn the openId Connect off and enable the plain OAuth authentication for Authorization Code flow.
-Alan-
You specify this by using scope parameter values. For OpenID Connect, scope value is set to openid. This is what specification says about authorisation request.
But there are some Azure AD specifics you need to taken care of. This is highlighted in Azure AD documentation. For OpenID Connect,
When your web application needs to authenticate the user, it must direct the user to the /authorize endpoint. This request is similar to the first leg of the OAuth 2.0 Authorization Code Flow, with a few important distinctions:
The request must include the scope openid in the scope parameter.
The response_type parameter must include id_token.
The request must include the nonce parameter.
Also here is the link for OAuth 2.0 documentation. You may not simply remove OpenID Connect specific parameters to get OAuth 2.0 response as there could be implementation specific requirements.

How to configure Swagger in Web API 2 to POST OAuth2 authentication request?

I have recently started a Web API 2 project in Visual Studio 2012 using OWIN middleware to authenticate users with OAuth2. I incorporated token based authentication as outlined on this tutorial (Token Based Authentication). The authentication part works great. I have added some testing API methods and I wanted to hook up Swagger for my API documentation. I got that part working too, with the exception that the API calls from Swagger fail on authorization.
After research, I found Erik Dahl's post about how to hook up Swagger to OWIN middleware. After I configured my Swagger according to the post, I now see the authenticate buttons on the Swagger UI next to each API method. However, when trying to authenticate, the authentication within Swagger is done using a GET request. The authentication on the web API though requires it to be POST request. Is it possible to configure Swagger make the authentication request a POST? If not, should I allow my API to accept GET requests for token authentication? What would be the best approach to make this work?
Note: The request still hits my authentication logic, but the client_id and client_secret are not passed in a GET request, only in a POST request.
Here's my Swagger config:
httpConfig
.EnableSwagger(c =>
{
c.SingleApiVersion("v1", "Sample API");
c.ApiKey("token")
.Description("API Key Authentication")
.Name("Bearer")
.In("header");
c.OAuth2("oauth2")
.AuthorizationUrl("/oauth/token")
.Flow("implicit")
.Description("OAuth2 authentication")
.Scopes(scopes =>
{
scopes.Add("sampleapi", "Sample API");
});
c.OperationFilter<AssignOAuth2SecurityRequirements>();
})
.EnableSwaggerUi(c =>
{
c.EnableOAuth2Support(
clientId: "Sample_App",
clientSecret: "xxxxx",
realm: "test-realm",
appName: "Swagger UI");
});
And here's my OAuth config:
app.CreatePerOwinContext<ApiClientRepo>(ApiClientRepo.Create);
app.CreatePerOwinContext<MeetingRegistrantRepo>(MeetingRegistrantRepo.Create);
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
//For Dev enviroment only (on production should be AllowInsecureHttp = false)
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/oauth/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new CustomOAuthProvider(),
AccessTokenFormat = new CustomJwtFormat("http://localhost:51071"),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
// OAuth 2.0 Bearer Access Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
No, I would not change the authentication method from POST to GET just to satisfy Swagger.
I found another article which should help you do what you want to do here : http://danielwertheim.se/use-identityserver-in-swaggerui-to-consume-a-secured-asp-net-webapi/
It wold be worth to try it that way. Don't forget that changing from POST to GET means you can no longer pass the parameters in the body of the request and you will instead have to do it in the URL of the request and that makes the whole thing insecure.
Yes, the ClientID and ClientSecret will still be part of the Authorization Header, but still do not open yourself up to stuff like this. Swagger should not dictate the architecture of your API so don't go there.