How to authenticate from automated tests against WebAPI with Owin security? - authentication

I have a .net web api app with owin security. The security middleware is configured with Google authentication (no local username/password authentication) and Oauth bearer token.
In startup:
public void ConfigureAuth(IAppBuilder app)
{
...
app.UseOAuthBearerTokens(new OAuthAuthnorizationServerOptions {
TokenEndpointPath = new PathString("/token"),
AuthorizeEndpointPath = new PathString("/account/authorize"),
Provider = new ApplicationOAuthProvider("web"),
...
});
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions {
ClientID = "...",
ClientSecret = "..."
});
}
From my client applications I go to
http://myapp.com/account/authorize?client_id=web&response_type=token&redirect_uri=...
This redirects to a google login that the user fills in, then back to my app with a bearer token that I peel off and add to request headers for my api.
I would like to write some integration tests where my tests call the API. The tests have the google credentials of a test user, but I am not sure how to have the test authenticate without bringing up a browser and a google login screen. I could drive that with Selenium but that seems heavy-handed.
How do I programmatically get a bearer token to use with my API such that I can impersonate my test user?

Normally you would use the OAuth2 resource owner password credentials grant in this case, but Google does not seem to support that.
So your best option seems to be to use the authorization code grant to authenticate the test user, extract the refresh token from the token endpoint response. Then use the refresh token and client ID and secret to get an access token from your integration test.
See this for more information:
https://developers.google.com/identity/protocols/OAuth2InstalledApp

Related

Azure AD Client Credentials and Interactive in same ASP.NET Core API

I have an ASP.NET Core (3.1) web API with a couple of client UI apps. Authentication is via Azure AD, everything is working, using:
services.AddAuthentication()
.AddAzureADBearer(options => Configuration.Bind("AzureAD", options))
I also want to allow machine to machine API access using the Client Credentials flow. I can also get this working using the same app registration.
However, I need a way to validate which flow the request is using, as I want to expose functionality using Client Credentials API to API requests that I don't want interactive users to have access to.
What is the best way to make this work?
I have created a separate app registration in AAD that the Client Secret for the Client Credentials grant is on, and I have it adding permissions (as roles) to the token. And in the app registration for the API, I have granted permission to the Client Credentials app registration. But if I obtain a token with this flow, I can't authenticate. I have found that changing the scope in the token request to match the scope on the API app registration gives me a token that allows me to access the API, but then it is missing the app roles.
One the interactive token there are some user specific claims. So one workaround would be to check for the presence of these claims and disallow the functionality I want to restrict if they are present, but this seems a little hacky.
What else can I do? Is there a way to make both login flows work? Or another option that I've missed?
In case anyone else needs to get this working, I got it working by switching from:
services.AddAuthentication()
.AddAzureADBearer(options => Configuration.Bind("AzureAD", options))
to:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.MetadataAddress = $"https://login.microsoftonline.com/mydomain.onmicrosoft.com/.well-known/openid-configuration";
options.TokenValidationParameters.ValidateAudience = false;
});
There are some additional steps too, as mentioned in the question. I created a separate app registration in AAD, and in the app registration for the API granted permission to the new app registration. In the new app registration I had to edit the manifest to get the scope I wanted included as a role (scopes are only assigned to user tokens, not tokens obtained with the client credentials grant).
With the token working that has the role data, for requests to my restricted endpoint I can just check that it's there:
public bool ValidateScope(string scopeName)
{
return _httpContextAccessor.HttpContext.User.IsInRole(scopeName);
}
bool authorised = _clientCredentialsService.ValidateScope("restricted");
if (!authorised)
{
throw new UnauthorizedAccessException("Attempt to access restricted functionality as a regular user");
}
(I have a filter that picks up this exception and bubbles it up to the consumer as a 403).
If anyone else is doing this you can see I've set ValidateAudience to false, so you probably want to add some policies if you do this.

JWT authentication and login path

I have a MVC web application that uses JWT token to authenticate users. It calls an authentication/login API to generate and retrieve the token. Since we are in a single sign on setup there won't be a login page that I can redirect the users to. The authentication happens behind without users knowing based on some information. The only way I have found to make this API login call is by decorating the controller(s) in the web application with an Authorization filter and making the httpclient call to the login API in the filter. Is there any way to configure the login URL globally in the MVC application so that every controller will be protected. Something like the following in the cookie authentication that can be configured for the JWT authentication.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/User/Login";
options.LogoutPath = "/User/Logout";
});
Or what are the other places where I can make this httpClient call to the API to retrieve the token and subsequently authenticate the users. Let me know if what I explained here doesn't make sense.

How to integrate Social Login with existing .Net core Web API backend and Angular SPA frontend with working OpenIddict user/password and bearer token

TL;DR
Question: how to implement social login (OAuth2 authorization flow) with an existing SPA/Web API application that is based on identity, user/password, bearer token authentication?
I have an existing application that has:
Backend: .Net Core 2 Web API with Identity and OpenIddict services configured, with a working authentication process based on user/password challenge for bearer token.
Users are stored with Identity (AspNetUsers).
Part of the Startup.cs code
// Register the OpenIddict services.
services.AddOpenIddict()
.AddCore(options =>
{
options.UseEntityFrameworkCore().UseDbContext<ApplicationDbContext>();
})
.AddServer(options =>
{
options.UseMvc();
options.EnableTokenEndpoint("/connect/token");
options.AllowPasswordFlow();
options.AllowRefreshTokenFlow();
options.AcceptAnonymousClients();
options.RegisterScopes(
OpenIdConnectConstants.Scopes.OpenId,
OpenIdConnectConstants.Scopes.Email,
OpenIdConnectConstants.Scopes.Phone,
OpenIdConnectConstants.Scopes.Profile,
OpenIdConnectConstants.Scopes.OfflineAccess,
OpenIddictConstants.Scopes.Roles);
})
.AddValidation();
.
Frontend: SPA Angular 7 app that consumes this backend API and token authorization
So basically the current setup is, user inputs user/password to the SPA that invokes the backend /connect/token endpoint that validates the credentials and generates the token for the client.
And now I need to integrate Social Login (OAuth2 Authorization flow) so that
user chooses login with provider,
gets redirected to providers authorization page,
gets redirected back to my application that
needs to create the Identity user and save the Identity UserLoginInfo data and
provide my application token so that the user can login.
I understand the OAuth2 authorization flow that needs to Request an Authorization Code and then Exchange Authorization Code for an Access Token for that provider. I also know that this flow must use backend, once it uses sensitive information like client_secret that can't be stored in client side.
But at some point user needs to interact with frontend, so connecting these parts seems very difficult considering that these are wide used technologies. All practical examples I found on Google were using .Net Core MVC application. I also found this article ASP.NET Core 3.0 Preview 4 - Authentication and authorization for SPAs that seems promising but is still in Preview 4.
I already created the social providers apps and I have client_id, client_secret. Also registered my redirects url's.
What I tried with no success was:
In frontend user chooses login with social provider,
User gets redirected to provider authorization page, authenticates himself and
gets redirected from the provider to my frontend URL (redirect_uri) with the provider's code then
my frontend calls my backend /connect/token existing endpoint passing the selected provider and the received code, the endpoint was programmed to receive the provider and code also, then
my backend calls provider's get AccessToken url posting "grant_type", "authorization_code" "code", code "redirect_uri", "https://same_frontend_host/same/path" "client_id", providerClientId "client_secret", providerSecret and receives a StatusCode: 401, ReasonPhrase: 'Unauthorized' response
What am I doing wrong? It's been a real hard time to get this to work.
What worked but it's not what I need
An implicit 2 step authorization flow using frontend for provider authentication calls and a backend call to get my bearer token and create Identity user. With this setup user made a successful login using a social provider, unfortunately it's not what I need
EDIT:
Made a diagram of what is implemented, it is failing at step 5/6 with StatusCode: 401, ReasonPhrase: 'Unauthorized' and further steps are not completed.
The flow you describe pretty much corresponds to "Authorization Cross Domain Code", an OpenID Connect flow that has never been standardized.
I wouldn't recommend going with such a non-standard option. Instead, consider tweaking your flow to make your JS client exclusively communicate with your own authorization server instead of starting the flow by making the client redirect the user agent to an external provider.
The key idea here is that your own authorization server should initiate the initial communication with the external provider (i.e it should build the authorization request and redirect your users to the external provider's authorization endpoint) and handle the last part: the callback authorization response. For that, I'd recommend going with the OAuth2/OIDC handlers shipping with ASP.NET Core (there are providers for Google, Facebook and many more)
Of course, this doesn't mean your JS client can't send a hint about the external provider the user should use to authenticate. It's something you can easily handle in your authorization controller. Here's an example:
public class AuthorizationController : Controller
{
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider;
private readonly SignInManager<ApplicationUser> _signInManager;
public AuthorizationController(
IAuthenticationSchemeProvider authenticationSchemeProvider,
SignInManager<ApplicationUser> signInManager)
{
_authenticationSchemeProvider = authenticationSchemeProvider;
_signInManager = signInManager;
}
[HttpGet("~/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIdConnectRequest request)
{
Debug.Assert(request.IsAuthorizationRequest(),
"The OpenIddict binder for ASP.NET Core MVC is not registered. " +
"Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");
if (!User.Identity.IsAuthenticated)
{
// Resolve the optional provider name from the authorization request.
// If no provider is specified, call Challenge() to redirect the user
// to the login page defined in the ASP.NET Core Identity options.
var provider = (string) request.GetParameter("identity_provider");
if (string.IsNullOrEmpty(provider))
{
return Challenge();
}
// Ensure the specified provider is supported.
var schemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
if (!schemes.Any(scheme => scheme.Name == provider))
{
return Challenge();
}
// When using ASP.NET Core Identity and its default AccountController,
// the user must be redirected to the ExternalLoginCallback action
// before being redirected back to the authorization endpoint.
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider,
Url.Action("ExternalLoginCallback", "Account", new
{
ReturnUrl = Request.PathBase + Request.Path + Request.QueryString
}));
return Challenge(properties, provider);
}
// ...
}
}

Using Google OAuth to secure web services in aspnet core

I'm getting lost in OAuth and OpenIDConnect and aspnet core middleware. Any help on this would be appreciated.
I have multiple UIs (web, native apps) that use the same set of web services, and I'd like to ensure only authenticated users can access the web services. My organization uses Google accounts, so I'd like to use Google authentication restricted to the organization domain.
The web site is properly requiring authentication, following this sample. What I need now is to have the web site (AngularJS 4) invoke my back end web services with an auth token that I can verify with Google.
The back end services are written with aspnet core. I've tried using these approaches: Google middleware and Google OpenIDConnect but these still 1) assume there is a UI that can prompt an unauthorized user to log in, and 2) appear to be cookie-based, and I won't have cookies for the web service calls.
I don't want to prompt the user to log in, since the "user" in this case is a software client. Either they're authenticated or not already. I just need to get the authentication token, validate it, and carry on.
This appears to be the same question, which hasn't been answered yet, either.
Any suggestions are appreciated. Also, suggestions or tips on having native apps do the same!
Got it working. As mentioned, I was getting lost, and the OpenIDConnect, though referenced in several areas as a solution, was a red herring for the web services. Here's what is working for me now, with as complete steps as I can provide (some cleanup required):
Add authentication to the UI following these directions
Obtain the JWT token as shown in the first segment here
On each web service call, include the JWT token in the headers:
Name: Authentication
Value: Bearer {token value}
Install the JwtBearer NuGet package
In the ConfigureServices method of Startup in the web service, after you AddMvc():
services.AddAuthorization(options =>
{ // this policy needed only if you want to restrict to accounts within your domain. otherwise, don't use options. or use whatever options work for you.
options.AddPolicy("hd",
policy => policy.RequireAssertion(context =>
context.User.HasClaim(c =>
c.Type == "hd" &&
("https://accounts.google.com".Equals(c.Issuer) ||
"accounts.google.com".Equals(c.Issuer, StringComparison.CurrentCultureIgnoreCase)) &&
c.Value == "yourdomain.com"
)));
});
In the Configure method, before you UseMvc():
JwtBearerOptions jwtOptions = new JwtBearerOptions();
jwtOptions.Audience = "{the OAuth 2.0 client ID credential from google api developer console}";
jwtOptions.Authority = "https://accounts.google.com";
jwtOptions.TokenValidationParameters = new TokenValidationParameters();
jwtOptions.TokenValidationParameters.ValidIssuers = new List<string>()
{
"https://accounts.google.com",
"accounts.google.com"
};
app.UseJwtBearerAuthentication(jwtOptions);
Perhaps there is a more appropriate way to do this...if there is, I'm interested in trying it out. For now, this is working.
I will try to help.
First you need to look at OpenID Connect (which is built on top of OAuth 2.0) remembering that OAuth 2.0 NOT an Authentication protocol.
1) assume there is a UI
No UI is required for login assuming you are using Google services. You only need to check for the existence of and validate the Access Token, Identity Token (and perhaps the refresh token). If there is no Token, assume the user is NOT Authenticated and redirect them to the Authentication Server with a Authorization Request.
If there is a valid Access Token and Refresh Token, then you can assume the user is Authenticated.
You can also inspect the Access Token for proper "Scopes" to determine if they are Authorized for your specific application.
If you are using Google for Authorization Server, you can validate the the hd parameter within Identity Token has the desired Domain.
BTW: No cookies involved.
Hope that helps.

Firebase custom OAuth authentication

FireBase appears to support OAuth-based authentication from Facebook, Twitter, Google, GitHub. What about a different OAuth provider? It would be great if there were a generic "OAuth" option where in addition to specifying an API Key and Secret you specified whatever other information was needed.
Is it possible to use FireBase's existing OAuth-based authentication modules without rolling my own custom OAuth authentication module for FireBase, by possibly forking from one of the "built-in" OAuth mechanisms? Which one is the most generic, if so?
I also struggled for a while now with this, and here's how I've done it for my project. Run a node.js express server that will have the role to:
get the req from your frontend app
redirect user to oauth page
return to node.js in case of success/error and compute the token needed for firebase in order to successfully login the user.
res with a cookie containing this token and redirect the user back to frontend app to complete the process.
You will have to run the node server on a different vps in order for your app to work but you'll probably need it anyway if you have a bigger app that needs to run private stuff on the backend and not everything upfront.
Firebase has 5 OAuth-based authentication, Facebook, Twitter, Google, GitHub and Custom Auth for now. Using Custom Authentication Firebase gives you complete control over user authentication by allowing you to authenticate using secure JSON Web Tokens (JWTs). So you can specify additional options to API Key and Secret. For example:
var logInAndThen = function(options) {
var secret = '********************';
var tokenGenerator = new FirebaseTokenGenerator(secret);
var token = tokenGenerator.createToken(credentials[options.userType ||
'admin'
};