Send a custom parameter to an external identity provider - owin

Is it possible to send a custom parameter to an external login provider/custom owin middleware
I have an Identity Server 3 who handles my authentication, and I have an external identity provider middleware which handles the signing in of the user,
to sign in I have to send a token to the middleware, which the middleware uses to authenticate.
I tried following approaches:
acr_values: I sent the token as a acr_value, but this approach does not work for 2 reasons:
The token is to long( >900 characters, even after setting the InputLengthRestrictions for the AcrValues to 51200 in the IdentityServerOptions).
The acr_values get stored in the sign in message which is not accessible in neither the ApplyResponseChallengeAsync()-Method nor in the AuthenticateCoreAsync()-Method
QueryString Parameter: I added a QueryString Parameter containing the token, this does not work either, because the parameter is not stored when redirecting to the external provider
Is there any way to store the parameter in a way that I can transfer it to the external IdProvider?
and/or can I access the SignInMessage during ApplyResponseChallengeAsync() or AuthenticateCoreAsync()?

There exists an extension method in the IdentityServer3 Nuget Package:
Context.Environment.GetSignInMessage(string signinId)
This can be used in the ApplyResponseChallengeAsync() method, because the signin Id is there passed as a queryString-parameter
And in the signInMessage you have access to the return url (including the QueryString parameter passed when redirecting to the identity provider) and the acr_values, and more.
So you just have to get the signin value from the Query and call GetSignInMessage()
protected override Task ApplyResponseChallengeAsync()
{
//...
string signInId = null;
IReadableStringCollection query = Request.Query;
var values = query.GetValues("signin");
if(values!= null && values.Count == 1)
{
signInId = values[0];
}
if (signInId == null)
return Task.FromResult<object>(null);
var signInMessage = Context.Environment.GetSignInMessage(signInId);
//...
}
So after all this was pretty easy, but it was hard to find as there is nearly no research material, except for this github issue(which is basically the source to this answer):
Custom values to external provider #1318

Related

Azure Functions app + Auth0 provider, getting 401 when calling API with auth token

I have read, and implemented local dev projects to match, Auth0's Complete Guide To React User Authentication with Auth0, successfully. I am confident in the implementation, given that all aspects of login and route protection are working correctly, as well as the local express server successfully authenticating API calls that use authentication tokens generated via the Auth0 React SDK.
I have added third button to the sample project's external-apis.js view for use in calling another API that I am trying to integrate with, which is an Azure Functions app. I would like to use Auth0 for this API in the same way I do for the express server, and take advantage of Azure's "Easy Auth" capabilities, as discussed in this MS doc. I have implemented an OpenID Connect provider, which points to my Auth0 application, in my Azure Function app per this MS doc.
This is what the function that calls this Azure Function app API looks like:
const callAzureApi = async () => {
try {
const token = await getAccessTokenSilently();
await fetch(
'https://example.azurewebsites.net/api/ExampleEndPoint',
{
method: 'GET',
headers: {
'content-type': 'application/json',
authorization: `Bearer ${token}`,
},
}
)
.then((response) => response.json())
.then((response) => {
setMessage(JSON.stringify(response));
})
.catch((error) => {
setMessage(error.message);
});
} catch (error) {
setMessage(error.message);
}
};
My issue is that making calls to this Azure Function app API always returns a 401 (Unuthorized) response, even though the authorization token is being sent. If I change the Authorization settings in the Azure portal to not require authentication, then the code correctly retrieves the data, so I'm confident that the code is correct.
But, is there something else I have missed in my setup in order to use Auth0 as my authentication provider for the backend in Azure?
Through continued documentation and blog reading, I was able to determine what was missing from my original implementation. In short, I was expecting a little too much after reading about tge "Easy Auth" features of Azure, at least when using an OpenID Connect provider like Auth0. Specifically, the validation of the JSON Web Token (JWT) does not come for free, and needed further implementation.
My app is using the React Auth0 SDK to sign the user in to the identity provider and get an authorization token to send in its API requests. The Azure documentation for client-directed sign-in flow discusses the ability to validate a JWT using a specific POST call to the auth endpoint with the JWT in the header, but even this feature seems out of reach here, given that OpenID Connect is not listed in the provider list, and my attempts at trying it anyway continued to yield nothing but 401s.
The answer, then, was to implement the JWT validation directly into the Azure function itself, and return the proper response only when the JWT in the request header can be validated. I would like to credit blog posts of Boris Wilhelm and Ben Chartrand for helping to get to this final understanding of how to properly use Auth0 for an Azure Functions backend API.
I created the following Security object to perform the token validation. The static nature of the ConfigurationManager is important for caching the configuration to reduce HTTP requests to the provider. (My Azure Functions project is written in C#, as opposed to the React JS front-end app.)
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;
namespace ExampleProject.Common {
public static class Security {
private static readonly IConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
private static readonly string ISSUER = Environment.GetEnvironmentVariable("Auth0Url", EnvironmentVariableTarget.Process);
private static readonly string AUDIENCE = Environment.GetEnvironmentVariable("Auth0Audience", EnvironmentVariableTarget.Process);
static Security()
{
var documentRetriever = new HttpDocumentRetriever {RequireHttps = ISSUER.StartsWith("https://")};
_configurationManager = new ConfigurationManager<OpenIdConnectConfiguration> (
$"{ISSUER}.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
documentRetriever
);
}
public static async Task<ClaimsPrincipal> ValidateTokenAsync(AuthenticationHeaderValue value) {
if(value?.Scheme != "Bearer")
return null;
var config = await _configurationManager.GetConfigurationAsync(CancellationToken.None);
var validationParameter = new TokenValidationParameters {
RequireSignedTokens = true,
ValidAudience = AUDIENCE,
ValidateAudience = true,
ValidIssuer = ISSUER,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
IssuerSigningKeys = config.SigningKeys
};
ClaimsPrincipal result = null;
var tries = 0;
while (result == null && tries <= 1) {
try {
var handler = new JwtSecurityTokenHandler();
result = handler.ValidateToken(value.Parameter, validationParameter, out var token);
} catch (SecurityTokenSignatureKeyNotFoundException) {
// This exception is thrown if the signature key of the JWT could not be found.
// This could be the case when the issuer changed its signing keys, so we trigger
// a refresh and retry validation.
_configurationManager.RequestRefresh();
tries++;
} catch (SecurityTokenException) {
return null;
}
}
return result;
}
}
}
Then, I added this small bit of boilerplate code toward the top of any HTTP-triggered functions, before any other code is run to process the request:
ClaimsPrincipal principal;
if ((principal = await Security.ValidateTokenAsync(req.Headers.Authorization)) == null) {
return new UnauthorizedResult();
}
With this in place, I finally have the implementation I was looking for. I'd like to improve the implementation with something more generic like a custom attribute, but I'm not sure that's possible yet either for OpenID Connect providers. Still, this is a perfectly acceptable solution for me, and gives me the level of security I was looking for when using a React front-end with an Azure Functions back-end.
Cheers!

Authorization for azure app resource management without client ID and client secret

I'm trying to create an application where the user can manage their Azure resources (sql databases, storage). How can I authorize the user without needing their client ID and client secret.
I've looked at their documentation: https://godoc.org/github.com/Azure/go-autorest/autorest/azure/auth, however all of them requires environment variables and/or clientID/clientSecret. Is there a way where a user can provide username/password and I can get back an authorizer
type Client struct {
ServersClient postgresql.ServersClient
}
func NewCloudClient() *Client {
return &Client{}
}
func (c *Client) Init(config map[string]string) error {
var (
subscriptionID = config["subscriptionID"]
// tenantID = config["tenantID"]
// clientID = config["clientID"]
// clientSecret = config["clientSecret"]
// resourceGroup = config["resourceGroup"]
)
// oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, tenantID)
// if err != nil {
// return errors.Wrap(err, "error getting OAuthConfig")
// }
// spt, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, resourceGroup)
serversClient := postgresql.NewServersClient(subscriptionID)
//serversClient.BaseClient.Authorizer = autorest.NewBearerAuthorizer(spt)
c.ServersClient = serversClient
return nil
}
In all cases of authorization flow you will need at least client id. In some you will need more like client secret.
You can read about OAuth authorization here https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-app-types
but in general what you are aiming for is either of two cases
Authorize as user without internaction using username and password (this would be client resource owner flow
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
Authorize as user with his interaction
if this is code that can't get postback URL then use device code flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code
or for anything in the browser use standard implicit grant flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow
You will quickly notice that in all cases you NEED to have client ID. This is because how Azure Authentication works.

Google OAuth flow - passing the resulting tokens via querystring

I'm reviewing some code that uses the OpenIdConnect OAuth flow in asp.net core to auth a user against Google. Once auth is established, there is a handler in OnTicketRecieved that takes the resulting access token and refresh token, and passes that to a different endpoint in the app:
OnTicketReceived = context =>
{
string returnUrl = "/Somewhere/else";
string refreshToken = context.Properties.Items.ContainsKey(".Token.refresh_token") ? context.Properties.Items[".Token.refresh_token"] : "none";
string accessToken = context.Properties.Items.ContainsKey(".Token.access_token") ? context.Properties.Items[".Token.access_token"] : "none";
var email = context.Principal.Claims.Where(c => c.Type == ClaimTypes.Email).Select(c => c.Value).SingleOrDefault();
returnUrl += $"?refreshToken={refreshToken}&accessToken={accessToken}&email={email}";
context.ReturnUri = returnUrl + WebUtility.UrlEncode(returnUrl);
return Task.CompletedTask;
},
My question is - how secure is this? It's effectively leaking refresh tokens that can be seen - although the scope of what you can do with them is very limited, being Google OAuth we're locked down to endpoints we can interact with, and the app secret would also need to be known in order to do anything malicious with these tokens
Thanks
Matt

Swagger authentication in Azure App Service

In my Azure Mobile .NET backend I want to use Azure Mobile .NET Server Swagger . I'm looking for fast way to hide swagger UI from public access ? Is there any way to provide access only for selected users ?
First a disclaimer: Even if you protect your Swagger UI from public consumption, you are not protecting your APIs from public consumption. You have to assume that everyone knows all of your routes and have the appropriate security in place to protect any requests that may come in.
That being said, there's still not a simple way to do this. Swashbuckle (the piece that adds Swagger to Web API) adds a custom HttpMessageHandler to the /swagger/ui route (as seen here). If you look at the Web API pipeline, you can see that if you specify a custom handler, you can bypass all of the Controller selection, Auth filters, etc. This is what happens here.
Some solutions:
Use an app setting to conditionally call ConfigureSwagger(config) in debug modes only. This would prevent all /swagger routes from making it into production. Or you could use a staging slot and only add it there.
You can wrap the SwaggerUiHandler with something like this Basic Auth MessageHandler. This would prompt the user for basic creds if they went to the /swagger/ui route. See below for my modified version of this code.
Maybe with a little more thought we can come up with a better solution -- I see a couple of issues (here and here) in the Swashbuckle repo that indicate you're not the first one to hit this.
Modified BasicAuthHandler (from here):
Warning: minimally tested (and be sure to change how you verify user/pass)
public class BasicAuthMessageHandler : DelegatingHandler
{
private const string BasicAuthResponseHeader = "WWW-Authenticate";
private const string BasicAuthResponseHeaderValue = "Basic";
public BasicAuthMessageHandler(HttpMessageHandler innerHandler)
{
this.InnerHandler = innerHandler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AuthenticationHeaderValue authValue = request.Headers.Authorization;
HttpResponseMessage unauthorizedResponse = request.CreateUnauthorizedResponse();
if (authValue != null && !string.IsNullOrWhiteSpace(authValue.Parameter))
{
Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
if (parsedCredentials != null)
{
// TODO: Check that the user/pass are valid
if (parsedCredentials.Username == "user" &&
parsedCredentials.Password == "pass")
{
// If match, pass along to the inner handler
return base.SendAsync(request, cancellationToken);
}
}
}
else
{
// Prompt for creds
unauthorizedResponse.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
}
return Task.FromResult(unauthorizedResponse);
}
private Credentials ParseAuthorizationHeader(string authHeader)
{
string[] credentials = Encoding.ASCII.GetString(Convert
.FromBase64String(authHeader))
.Split(
new[] { ':' });
if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0])
|| string.IsNullOrEmpty(credentials[1])) return null;
return new Credentials()
{
Username = credentials[0],
Password = credentials[1],
};
}
}
Registering with Swagger route
// Do this after calling ConfigureSwagger
ConfigureSwagger(config);
// Remove the swagger_ui route and re-add it with the wrapped handler.
var route = config.Routes["swagger_ui"];
config.Routes.Remove("swagger_ui");
config.Routes.MapHttpRoute("swagger_ui", route.RouteTemplate, route.Defaults, route.Constraints, new BasicAuthMessageHandler(route.Handler));

context.Request.User is null in OWIN OAuthAuthorizationServerProvider

I'm trying to implement OAuth using OWIN for a Web API v2 endpoint on my local intranet. The API is hosted in IIS using built-in Windows Authentication. In short, this is what I want to happen.
When I ask for my Token at /token
Pull the WindowsPrincipal out of the OWIN context
Use the SID from the WindowsPrincipal to look up some roles for this
user in a SQL table.
Create a new ClaimsIdentity that stores the username and roles
Turn that into a Json Web Token (JWT) that I sent bak
When I request a resource from my API using my token
Convert the JWT Bearer token back to the ClaimsIdentity
Use that ClaimsIdentity for authorizing requests to the resource by
role
This way I don't have to do a database lookup for user roles on each
request. It's just baked into the JWT.
I think I'm setting everything up correctly. My Startup.Configuration method looks like this.
public void Configuration(IAppBuilder app)
{
// token generation
// This is what drives the action when a client connects to the /token route
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
// for demo purposes
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromHours(8),
AccessTokenFormat = GetMyJwtTokenFormat(),
Provider = new MyAuthorizationServerProvider()
});
//// token consumption
app.UseOAuthBearerAuthentication(
new OAuthBearerAuthenticationOptions()
{
Realm = "http://www.ccl.org",
Provider = new OAuthBearerAuthenticationProvider(),
AccessTokenFormat = GetMyJwtTokenFormat()
}
);
app.UseWebApi(WebApiConfig.Register());
}
MyAuthorizationServerProvider looks like this...
public class MyAuthorizationServerProvider : OAuthAuthorizationServerProvider
{
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
// Since I'm hosting in IIS with Windows Auth enabled
// I'm expecting my WindowsPrincipal to be here, but it's null :(
var windowsPrincipal = context.OwinContext.Request.User.Identity;
// windowsPrincipal is null here. Why?
// Call SQL to get roles for this user
// create the identity with the roles
var id = new ClaimsIdentity(stuff, more stuff);
context.Validated(id);
}
}
My problem is that context.Request.User is null here. I can't get to my WindowsPrincipal. If I create some other dummy middleware, I can get to the WindowsPrincipal without issue. Why is it null in this context? Am I doing something wrong?
Swap the order of UseOAuthAuthorizationServer and UseOAuthBearerAuthentication. UseOAuthBearerAuthentication calls UseStageMarker(PipelineStage.Authenticate); to make it (and everything before it) run earlier in the ASP.NET pipeline. User is null when you run during the Authenticate stage.

Resources