OAuth Implementation 2.0 Using .Net Core , Swagger - c#

I want to implement the OAuth 2.0 in my web application. In web application, I have used swagger and I implemented the following code.
c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows
{
Implicit = new OpenApiOAuthFlow
{
AuthorizationUrl = new Uri("https://myWebAppUrl", UriKind.Absolute),
TokenUrl = new Uri("https://myWebAppUrl"),
}
}
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "oauth2" }
},
new[] { "readaccess", "writeaccess" }
}
});
I added the above code and ran my project.
Then, On the click of Authorize I was able to see the "Authorize" button and When I clicked on "Authorize" button, I was redirected to my web app login page.
Now,
I want to validate the login credentials that the user will enter on the login page, So, I generated JWT token in my login API to validate the user's credentials.
I may have missed some steps here.
But, I am lost how to redirect back to the "Swagger Documentation" after the login is successful.
After the login is successful it redirects me to the dashboard (I don't want this).
Please help how can I proceed.

If you are using Identity Server than you can create additional client for swagger and set redirect uri to swagger. You can start with tutorial.

Related

How to make HTTP requests to an API controller with AuthorizedAttribute?

Short story
I have a Web API. I want to prevent unauthorized requests so I added [Authorize(Roles = "admin")] attribute to my ApiController.
How can I make requests to my own API from different apps in C# (from desktop app for example)?
Long story
I have a simple web app solution which contains 2 projects.
In project1 users can sign in via username and password and do stuff. Code for signing users is as shown here:
if (ModelState.IsValid)
{
var user = await _userManager.GetFromModelAsync(model); // just get user from DB
var claims = new List<Claim>
{
new Claim(ClaimsIdentity.DefaultNameClaimType, user.Code),
new Claim(ClaimsIdentity.DefaultRoleClaimType, user.Role?.Name)
};
var id = new ClaimsIdentity(
claims, "ApplicationCookie",
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
await _httpContextAccessor.HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(id)); // I use httpContextAccessor cause I have this code not in controller
}
With this authentication, the user can easily access to any controller with attribute [Authorize(Roles = "user")].
In Project2, I have Admin Web API which can change stuff that users did. I want to call this API from a desktop C# app and I also want that API to have an authorization requirement ([Authorize(Roles = "admin")] in my case).
For example, it looks something like this:
[Authorize(Roles = "user")]
public class AdminApiController : Controller
{
public async Task<IActionResult> Index()
{
return Ok("Ok");
}
}
And the question is: with such an authentication mechanism how should I make HTTP requests to such an API with HttpClient? The only way I know is to use WebClient and emulate authorization with UploadValues to special authorization form.
P.S. I tried setting HTTP Authorization header but it didn't work. Maybe the point is to use separate authentication mechanism for users and admins?
Any help on this is highly appreciated.
I think you can follow the authentication mechanism JWT(JSON web Token) instead of cookies as the desktop app is a different environment that not use a web browser
User authentication scenario
the desktop app login to web API by userName and password param
if passed generate a token for the authenticated user and back it in response
when desktop app hit API action decorated by authorizing role via token
For a full example check this Url
Here is an example code in desktop app:
using Newtonsoft.Json;
...
var client = new HttpClient();
var httpRequestMessage = new HttpRequestMessage
{
Method = HttpMethod.Post,
RequestUri = new Uri("https://api.domain.com/action"),
Headers = {
{ HttpRequestHeader.Authorization.ToString(), "Bearer xxxxxxxxxxxxxxxxxxx" },
{ HttpRequestHeader.Accept.ToString(), "application/json" },
{ "X-Version", "1" }
},
Content = new StringContent(JsonConvert.SerializeObject(svm))
};
var response = client.SendAsync(httpRequestMessage).Result;

Azure Active Directory login returns the signature key was not found

I have implemented authentication for my ASP.NET Core 3.0 Web API using Azure AD. When I use the [Authorize] attribute, I am getting a http 401 error response with the message
Bearer error="invalid_token", error_description="The signature key was not found"
My current ConfigureService() in Startup.cs looks like this:
options.AddSecurityDefinition("oauth", new OpenApiSecurityScheme()
{
Type = SecuritySchemeType.OAuth2,
Flows = new OpenApiOAuthFlows()
{
Implicit = new OpenApiOAuthFlow()
{
TokenUrl = new Uri($"https://login.microsoftonline.com/<mytenantid>/oauth2/v2.0/token"),
AuthorizationUrl = new Uri($"https://login.microsoftonline.com/TenantId/oauth2/v2.0/authorize", UriKind.RelativeOrAbsolute),
Scopes = { { "api://<myappid>/user_impersonation", "user_impersonation" } }
}
}
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.Authority = $"https://login.microsoftonline.com/<mytenantid>/v2.0";
o.Audience = "<myappid>";
o.TokenValidationParameters.ValidAudiences = new string[] { "<myappid>", $"api://<myappid>" };
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "oauth"
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
}
});
Please let me know if I am missing something
I have answered many similar questions before. According to your question, you expose the web api protected by Azure. Next, you need to create a client application and use the client application to call the api application.
Usually the 401 error means that the audience of your token does not match your api. When you use the token to call the api, you will receive a 401 unauthorized error. The access token is issued based on the audience, so you must Make sure to set the scope to your api when you request the token. Of course you can also parse the token, check the aud claim, and make sure it is the api you want to call.
I use the auth code flow to do a simple demonstration for you:
First expose the api of the api application and add the client application.
Next,under 'API permissions', give your front-end application access to your back-end api application:
Under 'API permissions' click on 'Add permission', then click on the 'My APIs' tab.
Find your backend application and select the appropriate scope.
Click 'Add permissions'.
Grant admin consent for your APIs.
Get token:
Parse the token:

OpenIddict authorization request not handled

I'm trying out OpenIddict 3.0 for use in a SSO app. I followed the steps in the documentation, created an Authorize controller, and added a test application. When I try to connect to authorize I get this exception:
System.InvalidOperationException: The authorization request was not handled. To handle authorization requests, create a class implementing 'IOpenIddictServerHandler' and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.
Alternatively, enable the pass-through mode to handle them at a later stage.
I can't find anything in the documentation or sample apps that explains what this means. What am I missing?
Here's my code so far. In Startup.cs:
services.AddOpenIddict()
.AddCore(o =>
{
o.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>();
})
.AddServer(o =>
{
o.SetTokenEndpointUris("/connect/token");
o.SetAuthorizationEndpointUris("/connect/authorize");
o.AllowAuthorizationCodeFlow();
o.RegisterScopes(OpenIddictConstants.Scopes.Email);
o.AcceptAnonymousClients();
o.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
o.UseAspNetCore()
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement();
})
.AddValidation(o =>
{
o.UseLocalServer();
o.UseAspNetCore();
});
And test app description:
var descriptor = new OpenIddictApplicationDescriptor
{
ClientId = "test-app",
DisplayName = "Test Application",
PostLogoutRedirectUris = { new Uri("https://oidcdebugger.com/debug") },
RedirectUris = { new Uri("https://oidcdebugger.com/debug") }
};
I'm testing with the OpenID Connect debugger.
To handle authorization requests in a MVC controller, you must tell OpenIddict's ASP.NET Core host to use the pass-through mode, exactly like what you did for the token endpoint:
services.AddOpenIddict()
.AddServer(options =>
{
options.UseAspNetCore()
.EnableAuthorizationEndpointPassthrough() // Add this line.
.EnableTokenEndpointPassthrough()
.DisableTransportSecurityRequirement();
});

How to integrate swagger with Azure Active Directory OAuth

I'm trying to setup Swagger in my AspNetCore 2.1 application using Azure Active Directory V2 but I cannot seem to get it right. I am able to configure the setup so that swagger prompts, redirects and successfully authenticates my client/user but when passing the bearer token to the server results in the error Bearer error="invalid_token", error_description="The signature is invalid". I have created a GitHub repository with the project I am trying to get work with all its configuration (https://github.com/alucard112/auth-problem)
I have managed to get the V1 endpoint working, by setting the resource to the Client Id of the AAD app, which results in the JWT token having the 'aud' set to the app client Id. In the V2 endpoint the 'aud' is being set to what I think is the Graph API resource '00000003-0000-0000-c000-000000000000'. I believe this is my problem at the moment, although am not 100 % sure. The V2 endpoints don't seem to have a way to define the audience like the V1 did unless of course there is some oversight from my side.
My Startup file is structured as follows:
The authentication is setup as the following:
services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
.AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
options.Authority = $"https://login.microsoftonline.com/{tenantId}";
options.TokenValidationParameters = new TokenValidationParameters
{
// In multi-tenant apps you should disable issuer validation:
ValidateIssuer = false,
// In case you want to allow only specific tenants,
// you can set the ValidIssuers property to a list of valid issuer ids
// or specify a delegate for the IssuerValidator property, e.g.
// IssuerValidator = (issuer, token, parameters) => {}
// the validator should return the issuer string
// if it is valid and throw an exception if not
};
});
And the swagger is setup as follows:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Info
{
Title = "Protected Api",
});
c.OperationFilter<SecurityRequirementsOperationFilter>();
//IMATE - StevensW
// Define the OAuth2.0 scheme that's in use (i.e. Implicit Flow)
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize",
TokenUrl = $"https://login.microsoftonline.com/common/{tenantId}/v2.0/token",
Scopes = new Dictionary<string, string>
{
{ "openid", "Unsure" },
{ "profile", "Also Unsure" }
}
});
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.OAuthClientId(Configuration.GetValue<string>("AzureAd:ClientId"));
c.OAuthAppName("Protected API");
// c.OAuthUseBasicAuthenticationWithAccessCodeGrant();
// NEVER set the client secret here. It will ve exposed in the html of the swagger page if you "view source" and its not needed for OpenID Auth
// c.OAuthClientSecret(Configuration.GetValue<string>("AzureAd:ClientId"));
});
I am hoping to configure the swagger UI to use AAD's V2 endpoint and allow for a multi-tenant login that allows successfully authenticated API calls to be executed. Any help or direction would be greatly appreciated.
I ended up fixing the problem I was having. Working through this post helped me understand my mistakes.
The first mistake was my actual AAD app registration. I had not set a scope for the application under "Expose an API". Because they deprecated the resource property in V2, the way you would set the resource was to create a scope with the format api"//{application ID}/{scope_name}. After I made this change my AAD application was now correctly configured.
After that, I needed to add an additional section to my startup file:
return services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
// This is an Azure AD v2.0 Web API
options.Authority += "/v2.0";
// The valid audiences are both the Client ID (options.Audience) and api://{ClientID}
options.TokenValidationParameters.ValidAudiences = new string[] { options.Audience, $"api://{options.Audience}" };
options.TokenValidationParameters.ValidateIssuer = false;
});
Note: the link above provided an alternative solution to turning off the validation of the issuer if anyone is interested.
My AppSettings file was also simplified by only needing to define the Instance, TenantId, and ClientId.
Then from a swagger perspective, I just needed to add an additional scope to the security definition matching the one I created in my AAD application.
c.AddSecurityDefinition("oauth2", new OAuth2Scheme
{
Type = "oauth2",
Flow = "implicit",
AuthorizationUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
TokenUrl = "https://login.microsoftonline.com/common/oauth2/v2.0/token",
Scopes = new Dictionary<string, string>
{
{ "openid", "Sign In Permissions" },
{ "profile", "User Profile Permissions" },
{ $"api://{clientId}/access_as_user", "Application API Permissions" }
}
});
After these changes my application is now working as expected.
for v2 endpoint, update the accessTokenAcceptedVersion in Manifest of AAD from null to 2. It will work.

Azure mobile apps custom authentication with Cordova

I currently have a backend solution for my app using Azure Mobile Apps. I have enabled facebook, twitter, google and Microsoft logins. I am attempting to add a custom login flow in addition to this. I have setup an Auth0 account and application and I am able to get a token and profile back from auth0 when I make the request in-app using auth0 lock widget.
I followed this guide: https://shellmonger.com/2016/04/08/30-days-of-zumo-v2-azure-mobile-apps-day-5-custom-authentication/ and got to the stage 'Custom JWT Verification in the Server' but this is where I am stuck...my backend is in C# not node.js so how do I do the equivalent to this tutorial and validate the JWT token and subsequently access the table controllers from my front end application using azureClient.login/azureClient.table?
EDIT: Okay so as you will see in the comment thread below with #AdrianHall I have been successful in generating a token from within my cordova app but my stumbling block is now getting the service to accept it without having to exchange tokens. This is possible according to the guide posted.
This is my client-side code which currently makes the auth call to auth0 and does some client side set up to get a userID and generate the 'currentUser' object containing the new token.
auth0.lock.show(auth0.options, function(err, profile, token) {
if (err) {
console.error('Error authenticating with Auth0: ', err);
alert(err);
} else {
debugger;
var userID;
if (profile.user_id.indexOf("auth0") > -1) {
userID = profile.user_id.replace("auth0|", "");
} else if (profile.user_id.indexOf("facebook") > -1) {
userID = profile.user_id.replace("facebook|", "");
} else if (profile.user_id.indexOf("twitter") > -1) {
userID = profile.user_id.replace("twitter|", "");
} else if (profile.user_id.indexOf("microsoft") > -1) {
userID = profile.user_id.replace("microsoft|", "");
} else if (profile.user_id.indexOf("google-oauth2") > -1) {
userID = profile.user_id.replace("google-oauth2|", "");
}
window.azureClient.currentUser = {
userId: userID,
profile: profile,
mobileServiceAuthenticationToken: token
};
//A client session has now been created which contains attributes relevant to the currently logged in user.
console.log("window.azureClient.currentUser", window.azureClient.currentUser);
window.localStorage.setItem("currentUser", JSON.stringify(window.azureClient.currentUser));
//Call the get profile function which will call our API to get the user's activities and bio etc.
getProfile();
}
Backend code
MobileAppSettingsDictionary
settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
//This middleware is intended to be used locally for debugging.By default, HostName will
//only have a value when running in an App Service application.
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings[""],
ValidAudiences = new[] { ConfigurationManager.AppSettings[""] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["https://domain.eu.auth0.com/"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
In the Azure Mobile Apps C# backend, there is an App_Start\Startup.Mobile.cs file with the following code:
MobileAppSettingsDictionary settings = config.GetMobileAppSettingsProvider().GetMobileAppSettings();
if (string.IsNullOrEmpty(settings.HostName))
{
// This middleware is intended to be used locally for debugging. By default, HostName will
// only have a value when running in an App Service application.
app.UseAppServiceAuthentication(new AppServiceAuthenticationOptions
{
SigningKey = ConfigurationManager.AppSettings["SigningKey"],
ValidAudiences = new[] { ConfigurationManager.AppSettings["ValidAudience"] },
ValidIssuers = new[] { ConfigurationManager.AppSettings["ValidIssuer"] },
TokenHandler = config.GetAppServiceTokenHandler()
});
}
The app.UseAppServiceAuthentication call sets up the configuration needed for decoding your JWT. You just need to understand what your Audience (the aud field in the JWT) and Issuer (the iss field in the JWT). In the auth0 case, Audience is your ClientId and Issuer is "https://your-domain-value" - the Client Secret is the signing key
You can verify an example JWT by cut-and-paste at https://jwt.io - this will show explicitly what the values should be and allow you to verify the signature.

Categories