How to configure Swagger in Web API 2 to POST OAuth2 authentication request? - asp.net-web-api2

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.

Related

OpenIddict support returning authorization code via GET request for postman

I have set up an Authorization Server using OpenIddict 3.1.1 (porting over an existing one that was using the older ASOS package directly). I believe I am most of the way there, because when using the client application, I am able to log in, give consent, redirect back to the client, and exchange the authorization code for an access token.
However, when I try to do the same using Postman's OAuth 2.0 authentication support, I am able to log in (and give consent), but when it completes and returns the authorization code, I receive an HTTP 403 from the https://oauth.pstmn.io/v1/callback that I am redirected to:
403 ERROR
The request could not be satisfied.
This distribution is not configured to allow the HTTP request method that was used for this request. The distribution supports only cachable requests. We can't connect to the server for this app or website at this time. There might be too much traffic or a configuration error. Try again later, or contact the app or website owner.
If you provide content to customers through CloudFront, you can find steps to troubleshoot and help prevent this error by reviewing the CloudFront documentation.
Generated by cloudfront (CloudFront)
Request ID: UAXpago6ISiqbgm9U_SVPwh96qz1qoveZWFd0Cra-2FximeWZiY2aQ==
From what I can tell, this is because OpenIddict is issuing a POST request back to the callback url. This works for my client application, but evidently is not supported by Postman.
What configuration tweak do I need to make to OpenIddict to support this in postman?
OpenIddict related config in Startup.ConfigureServices:
services.AddOpenIddict()
.AddCore(options => {
options.AddApplicationStore<ClientStore>();
options.UseEntityFramework()
.UseDbContext<OAuthServerDbContext>()
.ReplaceDefaultEntities<Client, Authorization, OAuthScope, Token, long>()
;
})
.AddServer(options => {
options.RegisterClaims();
options.RegisterScopes(OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Profile,
"user");
// flows
options.AllowAuthorizationCodeFlow();
options.AllowRefreshTokenFlow();
options.AllowPasswordFlow();
options.AllowHybridFlow();
// implicit is used by postman
options.AllowImplicitFlow();
var serviceProvider = options.Services.BuildServiceProvider();
var oauthConstants = serviceProvider.GetRequiredService<IOptions<OAuthConstants>>().Value;
var tokenLifetimes = serviceProvider
.GetRequiredService<IOptions<OpenIdConnectServerTokenLifetimeSettings>>().Value;
// security
options.SetAccessTokenLifetime(tokenLifetimes.AccessTokenLifetime)
.SetAuthorizationCodeLifetime(tokenLifetimes.AuthorizationCodeLifetime)
.SetIdentityTokenLifetime(tokenLifetimes.IdentityTokenLifetime)
.SetRefreshTokenLifetime(tokenLifetimes.RefreshTokenLifetime);
options.SetIssuer(new Uri("https://localhost/oauth/"));
// custom handlers added here
options.AddEventHandlers();
// certificate details hidden
options.AddEncryptionCertificate(certificate);
// endpoints
options.SetAuthorizationEndpointUris("/OpenIdConnect/Authorize");
options.SetLogoutEndpointUris("/OpenIdConnect/Logout", "/Account/Logout");
options.SetRevocationEndpointUris("/OpenIdConnect/Revoke");
options.SetTokenEndpointUris("/OpenIdConnect/Token");
options.SetCryptographyEndpointUris("/OpenIdConnect/JWKDoc");
options.SetUserinfoEndpointUris("/OpenIdConnect/UserInfo");
options.UseAspNetCore()
.EnableStatusCodePagesIntegration()
.EnableAuthorizationEndpointPassthrough()
//.EnableTokenEndpointPassthrough()
.EnableLogoutEndpointPassthrough()
.EnableUserinfoEndpointPassthrough()
;
})
.AddValidation(options => {
options.UseLocalServer();
options.UseAspNetCore();
var serviceProvider = options.Services.BuildServiceProvider();
var config = serviceProvider.GetRequiredService<IConfiguration>();
options.SetClientId(config.GetValue<string>(nameof(Settings.OAuthClientId)));
options.SetClientSecret(config.GetValue<string>(nameof(Settings.ClientSecret)));
// certificate details hidden
options.AddEncryptionCertificate(certificate);
});
Postman details:
Authorization
Token Name: Redacted
Grant Type: Authorization Code
Callback URL: disabled, https://oauth.pstmn.io/v1/callback
Authorize using browser: checked
Auth URL: https://localhost/oauth/OpenIdConnect/Authorize
Access Token URL: https://localhost/oauth/OpenIdConnect/Token
Client ID: redacted, but correct
Client Secret: redacted, but correct
Scope: openid offline_access
State:
Client Authentication: Send client credentials in body
edit: The response that it sends to the postman callback URI does include the authorization code in the body, but because of the 403 response, Postman doesn't parse that out and make the follow-up request to exchange the code for the token.
There is an option that you can set to control if the authorization code is received in the URL as a query string or in the body as a post. The option is response_mode and you control that as a client.
I believe if it is not set to response_mode=form_post, then you will get the code in the URL instead.
See the details about this parameter here.

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.

OpenID Connect from Swagger UI with PKCE and Okta in .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

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.

Authentication and Authorization with ASP.NET Core and Service Stack

I have a ASP.Net Core MVC Web App that users needs to logon to get the id_token from the IdentityServer4 and then that id_token will be passed to webapi implemented in ServiceStack to obtain the authorization code. The subsequent call to the webapi will use the authorization code.
So far what I have read is for the Web App, it should use openid cookie token (UseOpenIdConnectAuthentication). For the webapi, it should use the bearer token. My question is how I can pass that http only cookie token from the client side browser as a bearer token in the http header. As the cookie is http only, it can't be accessed by the Javascript. Moreover, the ASP.NET Core cookie middleware encrypts the cookie, can that encrypted cookie be decrypted by the ServiceStack webapi (if the cookie is passed to the webapi)?
Am I going in the right direction? Any suggestion is welcome.
Thanks
You can find an example of your scenario here: https://identityserver4.readthedocs.io/en/release/quickstarts/5_hybrid_and_api_access.html
The authorization code is only used to get access tokens from the identity server, it is not used to authenticate to APIs.
Here is how the flow should work:
User logs in at Identity Server
Your MVC app gets an authorization code and id token
The id token tells your MVC app who the user is
The authorization code is exchanged for an access token and refresh token with identity server for the API
Now the MVC app can make HTTP calls from its backend using the access token
Authentication cookie is created and returned to user
Front-end submits the authentication cookie with every request to MVC backend, which authenticates every request automatically that hits MVC, then when you want to call the API from there, get it as shown in the docs, and attach it to your requests
I think the point you are missing here is that once the user is logged in, you will get the access token in the response as well when you land back on the client application. If you are using Hybrid Flow, on the client app we configure it as
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
AuthenticationScheme = "oidc",
SignInScheme = "Cookies",
Authority = "http://localhost:5000",
RequireHttpsMetadata = false,
ClientId = "mvc",
ClientSecret = "secret",
ResponseType = "code id_token",
Scope = { "api1", "offline_access" },
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true
});
See the ResponseType we ask for code i.e the access code. So you need not to call or login again. Once you want to call your api just get the token like
var access_token = await HttpContext.Authentication.GetTokenAsync("access_token");
// call api
var client = new HttpClient();
client.SetBearerToken(access_token);
var response = await client.GetAsync("http://localhost:5001/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
And if you using Implicit flow, your front end can get the access token using oidc-client library and user.access_token will have it.